# # 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 # or http://www.opensolaris.org/os/licensing. # 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. ILBD= ilbd PROG= $(ILBD) MANIFEST= ilbd.xml ILBD_OBJS= ilbd_main.o ilbd_support.o ilbd_sg.o ilbd_rules.o ilbd_hc.o \ ilbd_nat.o ilbd_scf.o ILBD_SRCS= $(ILBD_OBJS:%.o=%.c) HDRS= ilbd.h LIB_INC= $(SRC)/lib/libilb/common OBJS= $(ILBD_OBJS) SRCS= $(ILBD_SRCS) ILBSUBDIRS= ilb include ../../../Makefile.cmd include ../../../Makefile.ctf include ../../Makefile.cmd-inet ROOTMANIFESTDIR= $(ROOTSVCNETWORK)/loadbalancer CHECKHDRS= $(HDRS:%.h=%.check) CPPFLAGS += -D_FILE_OFFSET_BITS=64 -I$(CMDINETCOMMONDIR) -D_REENTRANT CPPFLAGS += -I$(LIB_INC) CPPFLAGS += -I$(SRC)/uts/common CERRWARN += -Wno-switch CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off CSTD = $(CSTD_GNU99) # I18n POFILE = $(ILBD).po POFILES = $(ILBD_SRCS:%.c=%.po) all: TARGET= all install: TARGET= install clean: TARGET= clean clobber: TARGET= clobber LDLIBS += -lcmdutils -lsocket -lsecdb -lnsl -lilb -lscf -linetutil -lbsm .KEEP_STATE: all: $(PROG) $(ILBD): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(CTFMERGE_HOOK) $(POST_PROCESS) include ../Makefile.lib install: all $(ROOTLIBINETPROG) $(ETCILBDFILES) $(ROOTMANIFEST) check: $(CHKMANIFEST) $(CHECKHDRS) clean: $(RM) $(OBJS) $(POFILE): $(POFILES) $(RM) $@ $(CAT) $(POFILES) > $@ all install clean clobber: $(ILBSUBDIRS) include ../../../Makefile.targ $(ILBSUBDIRS): FRC @cd $@; pwd; $(MAKE) $(TARGET) FRC: # # 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 # or http://www.opensolaris.org/os/licensing. # 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. # PROG = ilb_probe OBJS = $(PROG:%=%.o) SRCS = $(OBJS:%.o=%.c) include ../../../../Makefile.cmd LDLIBS += -lsocket -lnsl .KEEP_STATE: all: $(PROG) include ../../Makefile.lib CERRWARN += $(CNOWARN_UNINIT) ROOTLIBINETILB = $(ROOTLIBINET)/ilb ROOTLIBINETILBPROG = $(PROG:%=$(ROOTLIBINETILB)/%) $(ROOTLIBINETILB): $(ROOTLIBINET) $(INS.dir) $(ROOTLIBINETILB)/%: % $(ROOTLIBINETILB) $(INS.file) install: all $(ROOTLIBINETILBPROG) clean: $(RM) $(OBJS) lint: lint_SRCS include ../../../../Makefile.targ /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * The following values are what ilbd will set argv[0] to. This determines * what type of probe to send out. */ #define PROBE_PING "ilb_ping" #define PROBE_PROTO "ilb_probe" /* The transport protocol to use in the probe. Value of argv[3]. */ #define PROTO_TCP "TCP" #define PROTO_UDP "UDP" enum probe_type { ping_probe, tcp_probe, udp_probe }; /* Load balance mode. Value of argv[4]. */ #define MODE_DSR "DSR" #define MODE_NAT "NAT" #define MODE_HALF_NAT "HALF_NAT" enum lb_mode { dsr, nat, half_nat }; /* Number of arguments to the command from ilbd. */ #define PROG_ARGC 7 /* Size of buffer used to receive ICMP packet */ #define RECV_PKT_SZ 256 /* * Struct to store the probe info (most is passed in using the argv[] array to * the command given by ilbd). The argv[] contains the following. * * argv[0] is either PROBE_PING or PROBE_PROTO * argv[1] is the VIP * argv[2] is the backend server address * argv[3] is the transport protocol used in the rule * argv[4] is the load balance mode, "DSR", "NAT", "HALF-NAT" * argv[5] is the probe port * argv[6] is the probe timeout * * The following three fields are used in sending ICMP ECHO probe. * * echo_id is the ID set in the probe * echo_seq is the sequence set in the probe * echo_cookie is the random number data in a probe * lport is the local port (in network byte order) used to send the probe */ typedef struct { enum probe_type probe; struct in6_addr vip; /* argv[1] */ struct in6_addr srv_addr; /* argv[2] */ int proto; /* argv[3] */ enum lb_mode mode; /* argv[4] */ in_port_t port; /* argv[5] */ uint32_t timeout; /* argv[6] */ uint16_t echo_id; uint16_t echo_seq; uint32_t echo_cookie; in_port_t lport; } probe_param_t; /* Global variable to indicate whether a timeout means success. */ static boolean_t timeout_is_good; /* SIGALRM handler */ /* ARGSUSED */ static void probe_exit(int s) { if (timeout_is_good) { (void) printf("0"); exit(0); } else { (void) printf("-1"); exit(255); } } /* * Checksum routine for Internet Protocol family headers (C Version) * (copied from ping.c) */ static ushort_t in_cksum(ushort_t *addr, int len) { int nleft = len; ushort_t *w = addr; ushort_t answer; ushort_t odd_byte = 0; int sum = 0; /* * Our algorithm is simple, using a 32 bit accumulator (sum), * we add sequential 16 bit words to it, and at the end, fold * back all the carry bits from the top 16 bits into the lower * 16 bits. */ while (nleft > 1) { sum += *w++; nleft -= 2; } /* mop up an odd byte, if necessary */ if (nleft == 1) { *(uchar_t *)(&odd_byte) = *(uchar_t *)w; sum += odd_byte; } /* * add back carry outs from top 16 bits to low 16 bits */ sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ sum += (sum >> 16); /* add carry */ answer = ~sum; /* truncate to 16 bits */ return (answer); } /* It is assumed that argv[] contains PROBE_ARGC arguments. */ static boolean_t parse_probe_param(char *argv[], probe_param_t *param) { int32_t port; int64_t timeout; struct in_addr v4addr; if (strcmp(basename(argv[0]), PROBE_PING) == 0) { param->probe = ping_probe; } else { if (strcmp(basename(argv[0]), PROBE_PROTO) != 0) return (B_FALSE); if (strcasecmp(argv[3], PROTO_TCP) == 0) { param->probe = tcp_probe; param->proto = IPPROTO_TCP; } else if (strcasecmp(argv[3], PROTO_UDP) == 0) { param->probe = udp_probe; param->proto = IPPROTO_UDP; } else { return (B_FALSE); } } if (strchr(argv[1], ':') != NULL) { if (inet_pton(AF_INET6, argv[1], ¶m->vip) == 0) return (B_FALSE); } else if (strchr(argv[1], '.') != NULL) { if (inet_pton(AF_INET, argv[1], &v4addr) == 0) return (B_FALSE); IN6_INADDR_TO_V4MAPPED(&v4addr, ¶m->vip); } else { return (B_FALSE); } /* * The address family of vip and srv_addr should be the same for * now. But in future, we may allow them to be different... So * we don't do a check here. */ if (strchr(argv[2], ':') != NULL) { if (inet_pton(AF_INET6, argv[2], ¶m->srv_addr) == 0) return (B_FALSE); } else if (strchr(argv[2], '.') != NULL) { if (inet_pton(AF_INET, argv[2], &v4addr) == 0) return (B_FALSE); IN6_INADDR_TO_V4MAPPED(&v4addr, ¶m->srv_addr); } else { return (B_FALSE); } if (strcasecmp(argv[4], MODE_DSR) == 0) param->mode = dsr; else if (strcasecmp(argv[4], MODE_NAT) == 0) param->mode = nat; else if (strcasecmp(argv[4], MODE_HALF_NAT) == 0) param->mode = half_nat; else return (B_FALSE); if ((port = atoi(argv[5])) <= 0 || port > USHRT_MAX) return (B_FALSE); param->port = port; if ((timeout = strtoll(argv[6], NULL, 10)) <= 0 || timeout > UINT_MAX) return (B_FALSE); param->timeout = timeout; return (B_TRUE); } /* * Set up the destination address to be used to send a probe based on * param. */ static int set_sockaddr(struct sockaddr_storage *addr, socklen_t *addr_len, void **next_hop, probe_param_t *param) { int af; struct in6_addr *param_addr; struct sockaddr_in *v4_addr; struct sockaddr_in6 *v6_addr; boolean_t nh = B_FALSE; switch (param->mode) { case dsr: param_addr = ¶m->vip; nh = B_TRUE; break; case nat: case half_nat: param_addr = ¶m->srv_addr; break; } if (IN6_IS_ADDR_V4MAPPED(param_addr)) { af = AF_INET; v4_addr = (struct sockaddr_in *)addr; IN6_V4MAPPED_TO_INADDR(param_addr, &v4_addr->sin_addr); v4_addr->sin_family = AF_INET; v4_addr->sin_port = htons(param->port); *addr_len = sizeof (*v4_addr); } else { af = AF_INET6; v6_addr = (struct sockaddr_in6 *)addr; v6_addr->sin6_family = AF_INET6; v6_addr->sin6_addr = *param_addr; v6_addr->sin6_port = htons(param->port); v6_addr->sin6_flowinfo = 0; v6_addr->sin6_scope_id = 0; *addr_len = sizeof (*v6_addr); } if (!nh) { *next_hop = NULL; return (af); } if (af == AF_INET) { ipaddr_t *nh_addr; nh_addr = malloc(sizeof (ipaddr_t)); IN6_V4MAPPED_TO_IPADDR(¶m->srv_addr, *nh_addr); *next_hop = nh_addr; } else { struct sockaddr_in6 *nh_addr; nh_addr = malloc(sizeof (*nh_addr)); nh_addr->sin6_family = AF_INET6; nh_addr->sin6_addr = param->srv_addr; nh_addr->sin6_flowinfo = 0; nh_addr->sin6_scope_id = 0; *next_hop = nh_addr; } return (af); } /* * Use TCP to check if the peer server is alive. Create a TCP socket and * then call connect() to reach the peer server. If connect() does not * return within the timeout period, the SIGALRM handler will be invoked * and tell ilbd that the peer server is not alive. */ static int tcp_query(probe_param_t *param) { int ret; int sd, af; struct sockaddr_storage dst_addr; socklen_t dst_addr_len; void *next_hop; hrtime_t start, end; uint32_t rtt; ret = 0; next_hop = NULL; af = set_sockaddr(&dst_addr, &dst_addr_len, &next_hop, param); if ((sd = socket(af, SOCK_STREAM, param->proto)) == -1) return (-1); /* DSR mode, need to set the next hop */ if (next_hop != NULL) { if (af == AF_INET) { if (setsockopt(sd, IPPROTO_IP, IP_NEXTHOP, next_hop, sizeof (ipaddr_t)) < 0) { ret = -1; goto out; } } else { if (setsockopt(sd, IPPROTO_IPV6, IPV6_NEXTHOP, next_hop, sizeof (struct sockaddr_in6)) < 0) { ret = -1; goto out; } } } timeout_is_good = B_FALSE; (void) alarm(param->timeout); start = gethrtime(); if (connect(sd, (struct sockaddr *)&dst_addr, dst_addr_len) != 0) { ret = -1; goto out; } end = gethrtime(); rtt = (end - start) / (NANOSEC / MICROSEC); if (rtt == 0) rtt = 1; (void) printf("%u", rtt); out: (void) close(sd); return (ret); } /* * Check if the ICMP packet is a port unreachable message in respnsed to * our probe. Return -1 if no, 0 if yes. */ static int check_icmp_unreach_v4(struct icmp *icmph, probe_param_t *param) { struct udphdr *udph; struct ip *iph; if (icmph->icmp_type != ICMP_UNREACH) return (-1); if (icmph->icmp_code != ICMP_UNREACH_PORT) return (-1); /* LINTED E_BAD_PTR_CAST_ALIGN */ iph = (struct ip *)((char *)icmph + ICMP_MINLEN); if (iph->ip_p != IPPROTO_UDP) return (-1); /* LINTED E_BAD_PTR_CAST_ALIGN */ udph = (struct udphdr *)((char *)iph + (iph->ip_hl << 2)); if (udph->uh_dport != htons(param->port)) return (-1); if (udph->uh_sport != param->lport) return (-1); /* All matched, it is a response to the probe we sent. */ return (0); } /* * Check if the ICMP packet is a reply to our echo request. Need to match * the ID and sequence. */ static int check_icmp_echo_v4(struct icmp *icmph, probe_param_t *param) { uint32_t cookie; in_port_t port; if (icmph->icmp_type != ICMP_ECHOREPLY) return (-1); if (icmph->icmp_id != param->echo_id) return (-1); if (icmph->icmp_seq != param->echo_seq) return (-1); bcopy(icmph->icmp_data, &cookie, sizeof (cookie)); if (cookie != param->echo_cookie) return (-1); bcopy(icmph->icmp_data + sizeof (cookie), &port, sizeof (port)); if (port != param->port) return (-1); /* All matched, it is a response to the echo we sent. */ return (0); } /* Verify if an ICMP packet is what we expect. */ static int check_icmp_v4(char *buf, ssize_t rcvd, probe_param_t *param) { struct ip *iph; struct icmp *icmph; /* * We can dereference the length field without worry since the stack * should not have sent up the packet if it is smaller than a normal * ICMPv4 packet. */ /* LINTED E_BAD_PTR_CAST_ALIGN */ iph = (struct ip *)buf; /* LINTED E_BAD_PTR_CAST_ALIGN */ icmph = (struct icmp *)((char *)iph + (iph->ip_hl << 2)); /* * If we sent an UDP probe, check if the packet is a port * unreachable message in response to our probe. * * If we sent an ICMP echo request, check if the packet is a reply * to our echo request. */ if (param->probe == udp_probe) { /* Is the packet large enough for further checking? */ if (rcvd < 2 * sizeof (struct ip) + ICMP_MINLEN + sizeof (struct udphdr)) { return (-1); } return (check_icmp_unreach_v4(icmph, param)); } else { if (rcvd < sizeof (struct ip) + ICMP_MINLEN) return (-1); return (check_icmp_echo_v4(icmph, param)); } } /* * Check if the ICMPv6 packet is a port unreachable message in respnsed to * our probe. Return -1 if no, 0 if yes. */ static int check_icmp_unreach_v6(icmp6_t *icmp6h, probe_param_t *param) { ip6_t *ip6h; struct udphdr *udph; if (icmp6h->icmp6_type != ICMP6_DST_UNREACH) return (-1); if (icmp6h->icmp6_code != ICMP6_DST_UNREACH_NOPORT) return (-1); /* LINTED E_BAD_PTR_CAST_ALIGN */ ip6h = (ip6_t *)((char *)icmp6h + ICMP6_MINLEN); if (ip6h->ip6_nxt != IPPROTO_UDP) return (-1); udph = (struct udphdr *)(ip6h + 1); if (udph->uh_dport != htons(param->port)) return (-1); if (udph->uh_sport != param->lport) return (-1); return (0); } /* * Check if the ICMPv6 packet is a reply to our echo request. Need to match * the ID and sequence. */ static int check_icmp_echo_v6(icmp6_t *icmp6h, probe_param_t *param) { char *tmp; uint32_t cookie; in_port_t port; if (icmp6h->icmp6_type != ICMP6_ECHO_REPLY) return (-1); if (icmp6h->icmp6_id != param->echo_id) return (-1); if (icmp6h->icmp6_seq != param->echo_seq) return (-1); tmp = (char *)icmp6h + ICMP6_MINLEN; bcopy(tmp, &cookie, sizeof (cookie)); if (cookie != param->echo_cookie) return (-1); tmp += sizeof (cookie); bcopy(tmp, &port, sizeof (port)); if (port != param->port) return (-1); /* All matched, it is a response to the echo we sent. */ return (0); } /* Verify if an ICMPv6 packet is what we expect. */ static int check_icmp_v6(char *buf, ssize_t rcvd, probe_param_t *param) { icmp6_t *icmp6h; /* LINTED E_BAD_PTR_CAST_ALIGN */ icmp6h = (icmp6_t *)(buf); /* * If we sent an UDP probe, check if the packet is a port * unreachable message. * * If we sent an ICMPv6 echo request, check if the packet is a reply. */ if (param->probe == udp_probe) { /* Is the packet large enough for further checking? */ if (rcvd < sizeof (ip6_t) + ICMP6_MINLEN + sizeof (struct udphdr)) { return (-1); } return (check_icmp_unreach_v6(icmp6h, param)); } else { if (rcvd < ICMP6_MINLEN) return (-1); return (check_icmp_echo_v6(icmp6h, param)); } } /* * Wait for an ICMP reply indefinitely. If we get what we expect, return 0. * If an error happnes, return -1. */ static int wait_icmp_reply(int af, int recv_sd, struct sockaddr_storage *exp_from, probe_param_t *param) { char buf[RECV_PKT_SZ]; socklen_t from_len; ssize_t rcvd; int ret; for (;;) { if (af == AF_INET) { struct sockaddr_in v4_from; from_len = sizeof (v4_from); if ((rcvd = recvfrom(recv_sd, buf, RECV_PKT_SZ, 0, (struct sockaddr *)&v4_from, &from_len)) < 0) { ret = -1; break; } /* Packet not from our peer, ignore it. */ if ((((struct sockaddr_in *)exp_from)->sin_addr.s_addr) != v4_from.sin_addr.s_addr) { continue; } if (check_icmp_v4(buf, rcvd, param) == 0) { ret = 0; break; } } else { struct sockaddr_in6 v6_from; from_len = sizeof (struct sockaddr_in6); if ((rcvd = recvfrom(recv_sd, buf, RECV_PKT_SZ, 0, (struct sockaddr *)&v6_from, &from_len)) < 0) { ret = -1; break; } if (!IN6_ARE_ADDR_EQUAL(&(v6_from.sin6_addr), &((struct sockaddr_in6 *)exp_from)->sin6_addr)) { continue; } if (check_icmp_v6(buf, rcvd, param) == 0) { ret = 0; break; } } } return (ret); } /* Return the local port used (network byte order) in a socket. */ static int get_lport(int sd, in_port_t *lport) { struct sockaddr_storage addr; socklen_t addr_sz; addr_sz = sizeof (addr); if (getsockname(sd, (struct sockaddr *)&addr, &addr_sz) != 0) return (-1); if (addr.ss_family == AF_INET) *lport = ((struct sockaddr_in *)&addr)->sin_port; else *lport = ((struct sockaddr_in6 *)&addr)->sin6_port; return (0); } /* * Use UDP to check if the peer server is alive. Send a 0 length UDP packet * to the peer server. If there is no one listening, the peer IP stack * should send back a port unreachable ICMP(v4/v6) packet. If the peer * server is alive, there should be no response. So if we get SIGALRM, * the peer is alive. */ static int udp_query(probe_param_t *param) { int ret; int send_sd, recv_sd, af; struct sockaddr_storage dst_addr; socklen_t addr_len; void *next_hop; char buf[1]; struct itimerval timeout; uint64_t tm; ret = 0; next_hop = NULL; af = set_sockaddr(&dst_addr, &addr_len, &next_hop, param); if ((send_sd = socket(af, SOCK_DGRAM, param->proto)) == -1) return (-1); if ((recv_sd = socket(af, SOCK_RAW, (af == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6)) == -1) { return (-1); } /* DSR mode, need to set the next hop */ if (next_hop != NULL) { if (af == AF_INET) { if (setsockopt(send_sd, IPPROTO_IP, IP_NEXTHOP, next_hop, sizeof (ipaddr_t)) < 0) { ret = -1; goto out; } } else { if (setsockopt(send_sd, IPPROTO_IPV6, IPV6_NEXTHOP, next_hop, sizeof (struct sockaddr_in6)) < 0) { ret = -1; goto out; } } } /* * If ilbd asks us to wait at most t, we will wait for at most * t', which is 3/4 of t. If we wait for too long, ilbd may * timeout and kill us. */ timeout.it_interval.tv_sec = 0; timeout.it_interval.tv_usec = 0; tm = (param->timeout * MICROSEC >> 2) * 3; if (tm > MICROSEC) { timeout.it_value.tv_sec = tm / MICROSEC; timeout.it_value.tv_usec = tm - (timeout.it_value.tv_sec * MICROSEC); } else { timeout.it_value.tv_sec = 0; timeout.it_value.tv_usec = tm; } timeout_is_good = B_TRUE; if (setitimer(ITIMER_REAL, &timeout, NULL) != 0) { ret = -1; goto out; } if (sendto(send_sd, buf, 0, 0, (struct sockaddr *)&dst_addr, addr_len) != 0) { ret = -1; goto out; } if ((ret = get_lport(send_sd, ¶m->lport)) != 0) goto out; /* * If the server app is listening, we should not get back a * response. So if wait_icmp_reply() returns, either there * is an error or we get back something. */ (void) wait_icmp_reply(af, recv_sd, &dst_addr, param); ret = -1; out: (void) close(send_sd); (void) close(recv_sd); return (ret); } /* * Size (in uint32_t) of the ping packet to be sent to server. It includes * a cookie (random number) + the target port. The cookie and port are used * for matching ping request since there can be many such ping packets sent * to different servers from the same source address and using the same VIP. * The last two bytes are for padding. * */ #define PING_PKT_LEN \ ((ICMP_MINLEN + 2 * sizeof (uint32_t)) / sizeof (uint32_t)) /* * Try to get a random number from the pseudo random number device * /dev/urandom. If there is any error, return (uint32_t)gethrtime() * as a back up. */ static uint32_t get_random(void) { int fd; uint32_t num; if ((fd = open("/dev/urandom", O_RDONLY)) == -1) return ((uint32_t)gethrtime()); if (read(fd, &num, sizeof (num)) != sizeof (num)) num = ((uint32_t)gethrtime()); (void) close(fd); return (num); } /* * Use ICMP(v4/v6) echo request to check if the peer server machine is * reachable. Send a echo request and expect to get back a echo reply. */ static int ping_query(probe_param_t *param) { int ret; int sd, af; struct sockaddr_storage dst_addr; socklen_t dst_addr_len; void *next_hop; hrtime_t start, end; uint32_t rtt; uint32_t buf[PING_PKT_LEN]; struct icmp *icmph; ret = 0; next_hop = NULL; af = set_sockaddr(&dst_addr, &dst_addr_len, &next_hop, param); if ((sd = socket(af, SOCK_RAW, (af == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6)) == -1) { return (-1); } /* DSR mode, need to set the next hop */ if (next_hop != NULL) { if (af == AF_INET) { if (setsockopt(sd, IPPROTO_IP, IP_NEXTHOP, next_hop, sizeof (ipaddr_t)) < 0) { ret = -1; goto out; } } else { if (setsockopt(sd, IPPROTO_IPV6, IPV6_NEXTHOP, next_hop, sizeof (struct sockaddr_in6)) < 0) { ret = -1; goto out; } } } bzero(buf, sizeof (buf)); icmph = (struct icmp *)buf; icmph->icmp_type = af == AF_INET ? ICMP_ECHO : ICMP6_ECHO_REQUEST; icmph->icmp_code = 0; icmph->icmp_cksum = 0; icmph->icmp_id = htons(gethrtime() % USHRT_MAX); icmph->icmp_seq = htons(gethrtime() % USHRT_MAX); param->echo_cookie = get_random(); bcopy(¶m->echo_cookie, icmph->icmp_data, sizeof (param->echo_cookie)); bcopy(¶m->port, icmph->icmp_data + sizeof (param->echo_cookie), sizeof (param->port)); icmph->icmp_cksum = in_cksum((ushort_t *)buf, sizeof (buf)); param->echo_id = icmph->icmp_id; param->echo_seq = icmph->icmp_seq; timeout_is_good = B_FALSE; (void) alarm(param->timeout); start = gethrtime(); if (sendto(sd, buf, sizeof (buf), 0, (struct sockaddr *)&dst_addr, dst_addr_len) != sizeof (buf)) { ret = -1; goto out; } if (wait_icmp_reply(af, sd, &dst_addr, param) != 0) { ret = -1; goto out; } end = gethrtime(); rtt = (end - start) / (NANOSEC / MICROSEC); if (rtt == 0) rtt = 1; (void) printf("%u", rtt); out: (void) close(sd); return (ret); } int main(int argc, char *argv[]) { probe_param_t param; int ret; /* ilbd should pass in PROG_ARGC parameters. */ if (argc != PROG_ARGC) { (void) printf("-1"); return (-1); } if (signal(SIGALRM, probe_exit) == SIG_ERR) { (void) printf("-1"); return (-1); } if (!parse_probe_param(argv, ¶m)) { (void) printf("-1"); return (-1); } switch (param.probe) { case ping_probe: ret = ping_query(¶m); break; case tcp_probe: ret = tcp_query(¶m); break; case udp_probe: ret = udp_query(¶m); break; } if (ret == -1) (void) printf("-1"); return (ret); } /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _ILBD_H #define _ILBD_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SGNAME_SZ 80 #define ILB_FMRI "svc:/network/loadbalancer/ilb:default" #define HC_ACTION ILB_SRV_DISABLED_HC #define ADMIN_ACTION ILB_SRV_DISABLED_ADMIN /* Max name and value length for scf properties */ #define ILBD_MAX_NAME_LEN ilbd_scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) #define ILBD_MAX_VALUE_LEN ilbd_scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH) /* Different events ILBD is interested in. */ typedef enum { ILBD_EVENT_NEW_REQ, /* New client request */ ILBD_EVENT_REQ, /* Client request comes in */ ILBD_EVENT_REP_OK, /* Reply channel to client is writeable */ ILBD_EVENT_PROBE, /* A HC returns some result */ ILBD_EVENT_TIMER /* ilbd_timer_q fired */ } ilbd_event_t; typedef enum { ILBD_SCF_RULE, /* prop group for rules */ ILBD_SCF_SG, /* prop group for servergroups */ ILBD_SCF_HC /* prop group for healthchecks */ } ilbd_scf_pg_type_t; typedef enum { ILBD_SCF_CREATE, ILBD_SCF_DESTROY, ILBD_SCF_ENABLE_DISABLE } ilbd_scf_cmd_t; typedef enum { ILBD_STRING, /* string */ ILBD_INT, /* int */ ILBD_ADDR_V4, /* ipv4 addr */ ILBD_ADDR_V6 /* ipv6 addr */ } ilbd_scf_data_type_t; typedef enum { stat_enable_server, stat_disable_server, stat_declare_srv_dead, stat_declare_srv_alive } ilbd_srv_status_ind_t; /* * All user struct pointer passed to port_associate() should have the first * field as ilbd_event_t. The following struct can be used to find the * event. */ typedef struct { ilbd_event_t ev; } ilbd_event_obj_t; typedef struct { ilbd_event_t ev; timer_t timerid; } ilbd_timer_event_obj_t; typedef struct ilbd_srv { list_node_t isv_srv_link; ilb_sg_srv_t isv_srv; #define isv_addr isv_srv.sgs_addr #define isv_minport isv_srv.sgs_minport #define isv_maxport isv_srv.sgs_maxport #define isv_flags isv_srv.sgs_flags #define isv_id isv_srv.sgs_id #define isv_srvID isv_srv.sgs_srvID } ilbd_srv_t; #define MAX_SRVCOUNT 1000 #define MAX_SRVID (MAX_SRVCOUNT - 1) #define BAD_SRVID (-1) typedef struct ilbd_sg { list_t isg_srvlist; /* list of ilbd_srv_t */ char isg_name[ILB_SGNAME_SZ]; int32_t isg_srvcount; int32_t isg_max_id; list_t isg_rulelist; /* list of ilbd_rule_t */ char isg_id_arr[MAX_SRVCOUNT]; /* for server ID allocation */ list_node_t isg_link; /* linkage for sg list */ } ilbd_sg_t; typedef struct ilbd_rule { list_node_t irl_link; list_node_t irl_sglink; ilbd_sg_t *irl_sg; ilb_rule_info_t irl_info; #define irl_flags irl_info.rl_flags #define irl_name irl_info.rl_name #define irl_vip irl_info.rl_vip #define irl_proto irl_info.rl_proto #define irl_ipversion irl_info.rl_ipversion #define irl_minport irl_info.rl_minport #define irl_maxport irl_info.rl_maxport #define irl_algo irl_info.rl_algo #define irl_topo irl_info.rl_topo #define irl_nat_src_start irl_info.rl_nat_src_start #define irl_nat_src_end irl_info.rl_nat_src_end #define irl_stickymask irl_info.rl_stickymask #define irl_conndrain irl_info.rl_conndrain #define irl_nat_timeout irl_info.rl_nat_timeout #define irl_sticky_timeout irl_info.rl_sticky_timeout #define irl_hcport irl_info.rl_hcport #define irl_hcpflag irl_info.rl_hcpflag #define irl_sgname irl_info.rl_sgname #define irl_hcname irl_info.rl_hcname } ilbd_rule_t; /* * Health check related definitions */ /* Default health check probe program provided */ #define ILB_PROBE_PROTO "/usr/lib/inet/ilb/ilb_probe" /* Command name (argv[0]) passed to ilb_probe to indicate a ping test */ #define ILB_PROBE_PING "ilb_ping" /* Use the first character of the rule's hcname to decide if rule has HC. */ #define RULE_HAS_HC(irl) ((irl)->irl_info.rl_hcname[0] != '\0') /* Type of probe test */ typedef enum { ILBD_HC_PING = 1, /* ICMP Echo probe */ ILBD_HC_TCP, /* TCP connect probe */ ILBD_HC_UDP, /* UDP packet probe */ ILBD_HC_USER /* User supplied probe */ } ilbd_hc_test_t; /* Struct representing a hc object in ilbd */ typedef struct { list_node_t ihc_link; /* List linkage */ ilb_hc_info_t ihc_info; /* Short hand for the fields inside ilb_hc_info_t */ #define ihc_name ihc_info.hci_name #define ihc_test ihc_info.hci_test #define ihc_timeout ihc_info.hci_timeout #define ihc_count ihc_info.hci_count #define ihc_interval ihc_info.hci_interval #define ihc_def_ping ihc_info.hci_def_ping ilbd_hc_test_t ihc_test_type; /* Type of probe test */ int ihc_rule_cnt; /* Num of rules associated with hc */ list_t ihc_rules; /* Rules associated with this hc */ } ilbd_hc_t; struct ilbd_hc_srv_s; /* * Struct representing a hc rule object * * hcr_link: list linkage * hcr_rule: pointer to the ilbd rule object * hcr_servers: list of servers of this rule */ typedef struct { list_node_t hcr_link; ilbd_rule_t const *hcr_rule; list_t hcr_servers; } ilbd_hc_rule_t; struct ilbd_hc_srv_s; /* * Struct representing a event of the probe process * * ihp_ev: the event type, which is ILBD_EVENT_PROBE * ihp_srv: pointer to the hc server object * ihp_pid: pid of the probe process * ihp_done: is ilbd done reading the output of the probe process */ typedef struct { ilbd_event_t ihp_ev; struct ilbd_hc_srv_s *ihp_srv; pid_t ihp_pid; boolean_t ihp_done; } ilbd_hc_probe_event_t; /* * ilbd_hc_srv_t state * * ihd_hc_def_pinging: the default ping should be run * ihd-hc_probing: the probe process should be started */ enum ilbd_hc_state { ilbd_hc_def_pinging, ilbd_hc_probing }; /* * Struct representing a server associated with a hc object * * shc_srv_link: list linkage * shc_hc: pointer to the hc object * shc_hc_rule: pointer to the hc rule object * shc_sg_srv: pointer to the server group object * shc_tid: timeout ID * shc_cur_cnt: number of times the hc probe has been run * shc_fail_cnt: number of consecutive probe failure * shc_status: health status * shc_rtt: rtt (in micro sec) to the backend server * shc_lasttimer: last time a probe sequence is executed * shc_nexttime: next time a probe sequence is executed * shc_state: hc probe state * shc_child_pid: pid of the probe process * shc_child_fd: fd to the output of the probe process * shc_ev: event object of the probe process * shc_ev_port: event port of the event object */ typedef struct ilbd_hc_srv_s { list_node_t shc_srv_link; ilbd_hc_t *shc_hc; ilbd_hc_rule_t *shc_hc_rule; ilb_sg_srv_t const *shc_sg_srv; iu_timer_id_t shc_tid; uint_t shc_cur_cnt; uint_t shc_fail_cnt; ilb_hc_srv_status_t shc_status; uint32_t shc_rtt; time_t shc_lasttime; time_t shc_nexttime; enum ilbd_hc_state shc_state; pid_t shc_child_pid; int shc_child_fd; ilbd_hc_probe_event_t *shc_ev; int shc_ev_port; } ilbd_hc_srv_t; /* * Structure for holding audit server and servergroup event * data. Not all events use all members of the structure. */ typedef struct audit_sg_event_data { int32_t ed_ipaddr_type; /* ADT_IPv4 or ADT_IPv6 */ uint32_t ed_server_address[4]; /* server's IP address */ char *ed_serverid; /* serverid. */ uint16_t ed_minport; /* server's minport */ uint16_t ed_maxport; /* server's maxport */ char *ed_sgroup; /* servergroup */ } audit_sg_event_data_t; /* Struct to store client info */ typedef struct { ilbd_event_t cli_ev; int cli_sd; struct passwd cli_pw; size_t cli_pw_bufsz; char *cli_pw_buf; ilbd_cmd_t cli_cmd; ilb_comm_t *cli_saved_reply; size_t cli_saved_size; ucred_t *cli_peer_ucredp; /* needed for auditing */ } ilbd_client_t; void ilbd_reply_ok(uint32_t *, size_t *); void ilbd_reply_err(uint32_t *, size_t *, ilb_status_t); ilb_status_t ilbd_check_client_config_auth(const struct passwd *); ilb_status_t ilbd_check_client_enable_auth(const struct passwd *); ilb_status_t ilbd_retrieve_names(ilbd_cmd_t, uint32_t *, size_t *); void i_setup_sg_hlist(void); void i_setup_rule_hlist(void); void logperror(const char *); ilb_status_t ilbd_add_server_to_group(ilb_sg_info_t *, int, const struct passwd *, ucred_t *); ilb_status_t ilbd_rem_server_from_group(ilb_sg_info_t *, int, const struct passwd *, ucred_t *); ilb_status_t ilbd_create_sg(ilb_sg_info_t *, int, const struct passwd *, ucred_t *); ilb_status_t ilbd_destroy_sg(const char *, const struct passwd *, ucred_t *); ilb_status_t ilbd_retrieve_sg_hosts(const char *, uint32_t *, size_t *); ilb_status_t ilbd_enable_server(ilb_sg_info_t *, const struct passwd *, ucred_t *); ilb_status_t ilbd_disable_server(ilb_sg_info_t *, const struct passwd *, ucred_t *); ilb_status_t ilbd_k_Xable_server(const struct in6_addr *, const char *, ilbd_srv_status_ind_t); ilb_status_t i_add_srv2krules(list_t *, ilb_sg_srv_t *, int); ilb_status_t i_rem_srv_frm_krules(list_t *, ilb_sg_srv_t *, int); int ilbd_get_num_krules(void); ilb_status_t ilbd_get_krule_names(ilbd_namelist_t **, int); ilb_status_t ilb_get_krule_servers(ilb_sg_info_t *); ilbd_sg_t *i_find_sg_byname(const char *); ilb_status_t i_check_srv2rules(list_t *, ilb_sg_srv_t *); ilb_status_t ilbd_address_to_srvID(ilb_sg_info_t *, uint32_t *, size_t *); ilb_status_t ilbd_srvID_to_address(ilb_sg_info_t *, uint32_t *, size_t *); ilb_status_t do_ioctl(void *, ssize_t); ilb_status_t ilbd_create_rule(ilb_rule_info_t *, int, const struct passwd *, ucred_t *); ilb_status_t ilbd_retrieve_rule(ilbd_name_t, uint32_t *, size_t *); ilb_status_t ilbd_destroy_rule(ilbd_name_t, const struct passwd *, ucred_t *); ilb_status_t ilbd_enable_rule(ilbd_name_t, const struct passwd *, ucred_t *); ilb_status_t ilbd_disable_rule(ilbd_name_t, const struct passwd *, ucred_t *); boolean_t is_debugging_on(void); ilb_status_t ilbd_sg_check_rule_port(ilbd_sg_t *, ilb_rule_info_t *); void ilbd_enable_debug(void); ilb_status_t ilb_map_errno2ilbstat(int); ilb_status_t i_attach_rule2sg(ilbd_sg_t *, ilbd_rule_t *); /* Logging routine and macros */ void ilbd_log(int, const char *, ...); #define logerr(...) ilbd_log(LOG_ERR, __VA_ARGS__) #define logdebug(...) ilbd_log(LOG_DEBUG, __VA_ARGS__) /* Health check manipulation routines */ void i_ilbd_setup_hc_list(void); ilb_status_t ilbd_create_hc(const ilb_hc_info_t *, int, const struct passwd *, ucred_t *); ilb_status_t ilbd_destroy_hc(const char *, const struct passwd *, ucred_t *); ilbd_hc_t *ilbd_get_hc(const char *); ilb_status_t ilbd_get_hc_info(const char *, uint32_t *, size_t *); ilb_status_t ilbd_get_hc_srvs(const char *, uint32_t *, size_t *); ilb_status_t ilbd_hc_associate_rule(const ilbd_rule_t *, int); ilb_status_t ilbd_hc_dissociate_rule(const ilbd_rule_t *); ilb_status_t ilbd_hc_add_server(const ilbd_rule_t *, const ilb_sg_srv_t *, int); ilb_status_t ilbd_hc_del_server(const ilbd_rule_t *, const ilb_sg_srv_t *); ilb_status_t ilbd_hc_enable_rule(const ilbd_rule_t *); ilb_status_t ilbd_hc_disable_rule(const ilbd_rule_t *); ilb_status_t ilbd_hc_enable_server(const ilbd_rule_t *, const ilb_sg_srv_t *); ilb_status_t ilbd_hc_disable_server(const ilbd_rule_t *, const ilb_sg_srv_t *); /* Health check timer routines */ void ilbd_hc_probe_return(int, int, int, ilbd_hc_probe_event_t *); void ilbd_hc_timer_init(int, ilbd_timer_event_obj_t *); void ilbd_hc_timeout(void); void ilbd_hc_timer_update(ilbd_timer_event_obj_t *); /* Show NAT info routines */ ilb_status_t ilbd_show_nat(void *, const ilb_comm_t *, uint32_t *, size_t *); void ilbd_show_nat_cleanup(void); /* Show sticky info routines */ ilb_status_t ilbd_show_sticky(void *, const ilb_comm_t *, uint32_t *, size_t *); void ilbd_show_sticky_cleanup(void); ilb_status_t ilbd_create_pg(ilbd_scf_pg_type_t, void *); ilb_status_t ilbd_destroy_pg(ilbd_scf_pg_type_t, const char *); ilb_status_t ilbd_change_prop(ilbd_scf_pg_type_t, const char *, const char *, void *); void ilbd_scf_str_to_ip(int, char *, struct in6_addr *); ilb_status_t ilbd_scf_ip_to_str(uint16_t, struct in6_addr *, scf_type_t *, char *); ilb_status_t ilbd_scf_add_srv(ilbd_sg_t *, ilbd_srv_t *); ilb_status_t ilbd_scf_del_srv(ilbd_sg_t *, ilbd_srv_t *); int ilbd_scf_limit(int); ilb_status_t ilbd_walk_rule_pgs(ilb_status_t (*)(ilb_rule_info_t *, int, const struct passwd *, ucred_t *), void *, void *); ilb_status_t ilbd_walk_sg_pgs(ilb_status_t (*)(ilb_sg_info_t *, int, const struct passwd *, ucred_t *), void *, void *); ilb_status_t ilbd_walk_hc_pgs(ilb_status_t (*)(const ilb_hc_info_t *, int, const struct passwd *, ucred_t *), void *, void *); void ilbd_algo_to_str(ilb_algo_t, char *); void ilbd_topo_to_str(ilb_topo_t, char *); void ilbd_ip_to_str(uint16_t, struct in6_addr *, char *); void cvt_addr(uint32_t *, int32_t, struct in6_addr); int ilberror2auditerror(ilb_status_t); #ifdef __cplusplus } #endif #endif /* _ILBD_H */ /* * 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 * or http://www.opensolaris.org/os/licensing. * 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. * Copyright 2012 Milan Jurik. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libilb_impl.h" #include "ilbd.h" /* Global list of HC objects */ list_t ilbd_hc_list; /* Timer queue for all hc related timers. */ static iu_tq_t *ilbd_hc_timer_q; /* Indicate whether the timer needs to be updated */ static boolean_t hc_timer_restarted; static void ilbd_hc_probe_timer(iu_tq_t *, void *); static ilb_status_t ilbd_hc_restart_timer(ilbd_hc_t *, ilbd_hc_srv_t *); static boolean_t ilbd_run_probe(ilbd_hc_srv_t *); #define MAX(a, b) ((a) > (b) ? (a) : (b)) /* * Number of arguments passed to a probe. argc[0] is the path name of * the probe. */ #define HC_PROBE_ARGC 8 /* * Max number of characters to be read from the output of a probe. It * is long enough to read in a 64 bit integer. */ #define HC_MAX_PROBE_OUTPUT 24 void i_ilbd_setup_hc_list(void) { list_create(&ilbd_hc_list, sizeof (ilbd_hc_t), offsetof(ilbd_hc_t, ihc_link)); } /* * Given a hc object name, return a pointer to hc object if found. */ ilbd_hc_t * ilbd_get_hc(const char *name) { ilbd_hc_t *hc; for (hc = list_head(&ilbd_hc_list); hc != NULL; hc = list_next(&ilbd_hc_list, hc)) { if (strcasecmp(hc->ihc_name, name) == 0) return (hc); } return (NULL); } /* * Generates an audit record for create-healthcheck, * delete-healtcheck subcommands. */ static void ilbd_audit_hc_event(const char *audit_hcname, const ilb_hc_info_t *audit_hcinfo, 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 audit_error; if ((ucredp == NULL) && (cmd == ILBD_CREATE_HC)) { /* * we came here from the path where ilbd incorporates * the configuration that is listed in SCF: * i_ilbd_read_config->ilbd_walk_hc_pgs-> * ->ilbd_scf_instance_walk_pg->ilbd_create_hc * We skip auditing in that case */ logdebug("ilbd_audit_hc_event: skipping auditing"); return; } if (adt_start_session(&ah, NULL, 0) != 0) { logerr("ilbd_audit_hc_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_CREATE_HC) flag = ADT_ilb_create_healthcheck; else if (cmd == ILBD_DESTROY_HC) flag = ADT_ilb_delete_healthcheck; if ((event = adt_alloc_event(ah, flag)) == NULL) { logerr("ilbd_audit_hc_event: adt_alloc_event failed"); exit(EXIT_FAILURE); } (void) memset((char *)event, 0, sizeof (adt_event_data_t)); switch (cmd) { case ILBD_CREATE_HC: event->adt_ilb_create_healthcheck.auth_used = NET_ILB_CONFIG_AUTH; event->adt_ilb_create_healthcheck.hc_test = (char *)audit_hcinfo->hci_test; event->adt_ilb_create_healthcheck.hc_name = (char *)audit_hcinfo->hci_name; /* * If the value 0 is stored, the default values are * set in the kernel. User land does not know about them * So if the user does not specify them, audit record * will show them as 0 */ event->adt_ilb_create_healthcheck.hc_timeout = audit_hcinfo->hci_timeout; event->adt_ilb_create_healthcheck.hc_count = audit_hcinfo->hci_count; event->adt_ilb_create_healthcheck.hc_interval = audit_hcinfo->hci_interval; break; case ILBD_DESTROY_HC: event->adt_ilb_delete_healthcheck.auth_used = NET_ILB_CONFIG_AUTH; event->adt_ilb_delete_healthcheck.hc_name = (char *)audit_hcname; break; } /* Fill in success/failure */ if (rc == ILB_STATUS_OK) { if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) { logerr("ilbd_audit_hc_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_hc_event: adt_put_event failed"); exit(EXIT_FAILURE); } } adt_free_event(event); (void) adt_end_session(ah); } /* * Given the ilb_hc_info_t passed in (from the libilb), create a hc object * in ilbd. The parameter ev_port is not used, refer to comments of * ilbd_create_sg() in ilbd_sg.c */ /* ARGSUSED */ ilb_status_t ilbd_create_hc(const ilb_hc_info_t *hc_info, int ev_port, const struct passwd *ps, ucred_t *ucredp) { ilbd_hc_t *hc; ilb_status_t ret = ILB_STATUS_OK; /* * ps == NULL is from the daemon when it starts and load configuration * ps != NULL is from client. */ if (ps != NULL) { ret = ilbd_check_client_config_auth(ps); if (ret != ILB_STATUS_OK) { ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC, ret, ucredp); return (ret); } } if (hc_info->hci_name[0] == '\0') { logdebug("ilbd_create_hc: missing healthcheck info"); ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC, ILB_STATUS_ENOHCINFO, ucredp); return (ILB_STATUS_ENOHCINFO); } hc = ilbd_get_hc(hc_info->hci_name); if (hc != NULL) { logdebug("ilbd_create_hc: healthcheck name %s already" " exists", hc_info->hci_name); ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC, ILB_STATUS_EEXIST, ucredp); return (ILB_STATUS_EEXIST); } /* * Sanity check on user supplied probe. The given path name * must be a full path name (starts with '/') and is * executable. */ if (strcasecmp(hc_info->hci_test, ILB_HC_STR_TCP) != 0 && strcasecmp(hc_info->hci_test, ILB_HC_STR_UDP) != 0 && strcasecmp(hc_info->hci_test, ILB_HC_STR_PING) != 0 && (hc_info->hci_test[0] != '/' || access(hc_info->hci_test, X_OK) == -1)) { if (errno == ENOENT) { logdebug("ilbd_create_hc: user script %s doesn't " "exist", hc_info->hci_test); ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC, ILB_STATUS_ENOENT, ucredp); return (ILB_STATUS_ENOENT); } else { logdebug("ilbd_create_hc: user script %s is " "invalid", hc_info->hci_test); ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC, ILB_STATUS_EINVAL, ucredp); return (ILB_STATUS_EINVAL); } } /* Create and add the hc object */ hc = calloc(1, sizeof (ilbd_hc_t)); if (hc == NULL) { ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC, ILB_STATUS_ENOMEM, ucredp); return (ILB_STATUS_ENOMEM); } (void) memcpy(&hc->ihc_info, hc_info, sizeof (ilb_hc_info_t)); if (strcasecmp(hc->ihc_test, ILB_HC_STR_TCP) == 0) hc->ihc_test_type = ILBD_HC_TCP; else if (strcasecmp(hc->ihc_test, ILB_HC_STR_UDP) == 0) hc->ihc_test_type = ILBD_HC_UDP; else if (strcasecmp(hc->ihc_test, ILB_HC_STR_PING) == 0) hc->ihc_test_type = ILBD_HC_PING; else hc->ihc_test_type = ILBD_HC_USER; list_create(&hc->ihc_rules, sizeof (ilbd_hc_rule_t), offsetof(ilbd_hc_rule_t, hcr_link)); /* Update SCF */ if (ps != NULL) { if ((ret = ilbd_create_pg(ILBD_SCF_HC, (void *)hc)) != ILB_STATUS_OK) { ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC, ret, ucredp); list_destroy(&hc->ihc_rules); free(hc); return (ret); } } /* Everything is fine, now add it to the global list. */ list_insert_tail(&ilbd_hc_list, hc); ilbd_audit_hc_event(NULL, hc_info, ILBD_CREATE_HC, ret, ucredp); return (ret); } /* * Given a name of a hc object, destroy it. */ ilb_status_t ilbd_destroy_hc(const char *hc_name, const struct passwd *ps, ucred_t *ucredp) { ilb_status_t ret; ilbd_hc_t *hc; /* * No need to check ps == NULL, daemon won't call any destroy func * at start up. */ ret = ilbd_check_client_config_auth(ps); if (ret != ILB_STATUS_OK) { ilbd_audit_hc_event(hc_name, NULL, ILBD_DESTROY_HC, ret, ucredp); return (ret); } hc = ilbd_get_hc(hc_name); if (hc == NULL) { logdebug("ilbd_destroy_hc: healthcheck %s does not exist", hc_name); ilbd_audit_hc_event(hc_name, NULL, ILBD_DESTROY_HC, ILB_STATUS_ENOENT, ucredp); return (ILB_STATUS_ENOENT); } /* If hc is in use, cannot delete it */ if (hc->ihc_rule_cnt > 0) { logdebug("ilbd_destroy_hc: healthcheck %s is associated" " with a rule - cannot remove", hc_name); ilbd_audit_hc_event(hc_name, NULL, ILBD_DESTROY_HC, ILB_STATUS_INUSE, ucredp); return (ILB_STATUS_INUSE); } if ((ret = ilbd_destroy_pg(ILBD_SCF_HC, hc_name)) != ILB_STATUS_OK) { logdebug("ilbd_destroy_hc: cannot destroy healthcheck %s " "property group", hc_name); ilbd_audit_hc_event(hc_name, NULL, ILBD_DESTROY_HC, ret, ucredp); return (ret); } list_remove(&ilbd_hc_list, hc); list_destroy(&hc->ihc_rules); free(hc); ilbd_audit_hc_event(hc_name, NULL, ILBD_DESTROY_HC, ret, ucredp); return (ret); } /* * Given a hc object name, return its information. Used by libilb to * get hc info. */ ilb_status_t ilbd_get_hc_info(const char *hc_name, uint32_t *rbuf, size_t *rbufsz) { ilbd_hc_t *hc; ilb_hc_info_t *hc_info; ilb_comm_t *ic = (ilb_comm_t *)rbuf; hc = ilbd_get_hc(hc_name); if (hc == NULL) { logdebug("%s: healthcheck %s does not exist", __func__, hc_name); return (ILB_STATUS_ENOENT); } ilbd_reply_ok(rbuf, rbufsz); hc_info = (ilb_hc_info_t *)&ic->ic_data; (void) strlcpy(hc_info->hci_name, hc->ihc_name, sizeof (hc->ihc_name)); (void) strlcpy(hc_info->hci_test, hc->ihc_test, sizeof (hc->ihc_test)); hc_info->hci_timeout = hc->ihc_timeout; hc_info->hci_count = hc->ihc_count; hc_info->hci_interval = hc->ihc_interval; hc_info->hci_def_ping = hc->ihc_def_ping; *rbufsz += sizeof (ilb_hc_info_t); return (ILB_STATUS_OK); } static void ilbd_hc_copy_srvs(uint32_t *rbuf, size_t *rbufsz, ilbd_hc_rule_t *hc_rule, const char *rulename) { ilbd_hc_srv_t *tmp_srv; ilb_hc_srv_t *dst_srv; ilb_hc_rule_srv_t *srvs; size_t tmp_rbufsz; int i; tmp_rbufsz = *rbufsz; /* Set up the reply buffer. rbufsz will be set to the new size. */ ilbd_reply_ok(rbuf, rbufsz); /* Calculate how much space is left for holding server info. */ *rbufsz += sizeof (ilb_hc_rule_srv_t); tmp_rbufsz -= *rbufsz; srvs = (ilb_hc_rule_srv_t *)&((ilb_comm_t *)rbuf)->ic_data; tmp_srv = list_head(&hc_rule->hcr_servers); for (i = 0; tmp_srv != NULL && tmp_rbufsz >= sizeof (*dst_srv); i++) { dst_srv = &srvs->rs_srvs[i]; (void) strlcpy(dst_srv->hcs_rule_name, rulename, ILB_NAMESZ); (void) strlcpy(dst_srv->hcs_ID, tmp_srv->shc_sg_srv->sgs_srvID, ILB_NAMESZ); (void) strlcpy(dst_srv->hcs_hc_name, tmp_srv->shc_hc->ihc_name, ILB_NAMESZ); dst_srv->hcs_IP = tmp_srv->shc_sg_srv->sgs_addr; dst_srv->hcs_fail_cnt = tmp_srv->shc_fail_cnt; dst_srv->hcs_status = tmp_srv->shc_status; dst_srv->hcs_rtt = tmp_srv->shc_rtt; dst_srv->hcs_lasttime = tmp_srv->shc_lasttime; dst_srv->hcs_nexttime = tmp_srv->shc_nexttime; tmp_srv = list_next(&hc_rule->hcr_servers, tmp_srv); tmp_rbufsz -= sizeof (*dst_srv); } srvs->rs_num_srvs = i; *rbufsz += i * sizeof (*dst_srv); } /* * Given a rule name, return the hc status of its servers. */ ilb_status_t ilbd_get_hc_srvs(const char *rulename, uint32_t *rbuf, size_t *rbufsz) { ilbd_hc_t *hc; ilbd_hc_rule_t *hc_rule; for (hc = list_head(&ilbd_hc_list); hc != NULL; hc = list_next(&ilbd_hc_list, hc)) { for (hc_rule = list_head(&hc->ihc_rules); hc_rule != NULL; hc_rule = list_next(&hc->ihc_rules, hc_rule)) { if (strcasecmp(hc_rule->hcr_rule->irl_name, rulename) != 0) { continue; } ilbd_hc_copy_srvs(rbuf, rbufsz, hc_rule, rulename); return (ILB_STATUS_OK); } } return (ILB_STATUS_RULE_NO_HC); } /* * Initialize the hc timer and associate the notification of timeout to * the given event port. */ void ilbd_hc_timer_init(int ev_port, ilbd_timer_event_obj_t *ev_obj) { struct sigevent sigev; port_notify_t notify; if ((ilbd_hc_timer_q = iu_tq_create()) == NULL) { logerr("%s: cannot create hc timer queue", __func__); exit(EXIT_FAILURE); } hc_timer_restarted = B_FALSE; ev_obj->ev = ILBD_EVENT_TIMER; ev_obj->timerid = -1; notify.portnfy_port = ev_port; notify.portnfy_user = ev_obj; sigev.sigev_notify = SIGEV_PORT; sigev.sigev_value.sival_ptr = ¬ify; if (timer_create(CLOCK_REALTIME, &sigev, &ev_obj->timerid) == -1) { logerr("%s: cannot create timer", __func__); exit(EXIT_FAILURE); } } /* * HC timeout handler. */ void ilbd_hc_timeout(void) { (void) iu_expire_timers(ilbd_hc_timer_q); hc_timer_restarted = B_TRUE; } /* * Set up the timer to fire at the earliest timeout. */ void ilbd_hc_timer_update(ilbd_timer_event_obj_t *ev_obj) { itimerspec_t itimeout; int timeout; /* * There is no change on the timer list, so no need to set up the * timer again. */ if (!hc_timer_restarted) return; restart: if ((timeout = iu_earliest_timer(ilbd_hc_timer_q)) == INFTIM) { hc_timer_restarted = B_FALSE; return; } else if (timeout == 0) { /* * Handle the timeout immediately. After that (clearing all * the expired timers), check to see if there are still * timers running. If yes, start them. */ (void) iu_expire_timers(ilbd_hc_timer_q); goto restart; } itimeout.it_value.tv_sec = timeout / MILLISEC + 1; itimeout.it_value.tv_nsec = 0; itimeout.it_interval.tv_sec = 0; itimeout.it_interval.tv_nsec = 0; /* * Failure to set a timeout is "OK" since hopefully there will be * other events and timer_settime() will be called again. So * we will only miss some timeouts. But in the worst case, no event * will happen and ilbd will get stuck... */ if (timer_settime(ev_obj->timerid, 0, &itimeout, NULL) == -1) logerr("%s: cannot set timer", __func__); hc_timer_restarted = B_FALSE; } /* * Kill the probe process of a server. */ static void ilbd_hc_kill_probe(ilbd_hc_srv_t *srv) { /* * First dissociate the fd from the event port. It should not * fail. */ if (port_dissociate(srv->shc_ev_port, PORT_SOURCE_FD, srv->shc_child_fd) != 0) { logdebug("%s: port_dissociate: %s", __func__, strerror(errno)); } (void) close(srv->shc_child_fd); free(srv->shc_ev); srv->shc_ev = NULL; /* Then kill the probe process. */ if (kill(srv->shc_child_pid, SIGKILL) != 0) { logerr("%s: rule %s server %s: %s", __func__, srv->shc_hc_rule->hcr_rule->irl_name, srv->shc_sg_srv->sgs_srvID, strerror(errno)); } /* Should not fail... */ if (waitpid(srv->shc_child_pid, NULL, 0) != srv->shc_child_pid) { logdebug("%s: waitpid: rule %s server %s", __func__, srv->shc_hc_rule->hcr_rule->irl_name, srv->shc_sg_srv->sgs_srvID); } srv->shc_child_pid = 0; } /* * Disable the server, either because the server is dead or because a timer * cannot be started for this server. Note that this only affects the * transient configuration, meaning only in memory. The persistent * configuration is not affected. */ static void ilbd_mark_server_disabled(ilbd_hc_srv_t *srv) { srv->shc_status = ILB_HCS_DISABLED; /* Disable the server in kernel. */ if (ilbd_k_Xable_server(&srv->shc_sg_srv->sgs_addr, srv->shc_hc_rule->hcr_rule->irl_name, stat_declare_srv_dead) != ILB_STATUS_OK) { logerr("%s: cannot disable server in kernel: rule %s " "server %s", __func__, srv->shc_hc_rule->hcr_rule->irl_name, srv->shc_sg_srv->sgs_srvID); } } /* * A probe fails, set the state of the server. */ static void ilbd_set_fail_state(ilbd_hc_srv_t *srv) { if (++srv->shc_fail_cnt < srv->shc_hc->ihc_count) { /* Probe again */ ilbd_hc_probe_timer(ilbd_hc_timer_q, srv); return; } logdebug("%s: rule %s server %s fails %u", __func__, srv->shc_hc_rule->hcr_rule->irl_name, srv->shc_sg_srv->sgs_srvID, srv->shc_fail_cnt); /* * If this is a ping test, mark the server as * unreachable instead of dead. */ if (srv->shc_hc->ihc_test_type == ILBD_HC_PING || srv->shc_state == ilbd_hc_def_pinging) { srv->shc_status = ILB_HCS_UNREACH; } else { srv->shc_status = ILB_HCS_DEAD; } /* Disable the server in kernel. */ if (ilbd_k_Xable_server(&srv->shc_sg_srv->sgs_addr, srv->shc_hc_rule->hcr_rule->irl_name, stat_declare_srv_dead) != ILB_STATUS_OK) { logerr("%s: cannot disable server in kernel: rule %s " "server %s", __func__, srv->shc_hc_rule->hcr_rule->irl_name, srv->shc_sg_srv->sgs_srvID); } /* Still keep probing in case the server is alive again. */ if (ilbd_hc_restart_timer(srv->shc_hc, srv) != ILB_STATUS_OK) { /* Only thing to do is to disable the server... */ logerr("%s: cannot restart timer: rule %s server %s", __func__, srv->shc_hc_rule->hcr_rule->irl_name, srv->shc_sg_srv->sgs_srvID); srv->shc_status = ILB_HCS_DISABLED; } } /* * A probe process has not returned for the ihc_timeout period, we should * kill it. This function is the handler of this. */ /* ARGSUSED */ static void ilbd_hc_kill_timer(iu_tq_t *tq, void *arg) { ilbd_hc_srv_t *srv = (ilbd_hc_srv_t *)arg; ilbd_hc_kill_probe(srv); ilbd_set_fail_state(srv); } /* * Probe timeout handler. Send out the appropriate probe. */ /* ARGSUSED */ static void ilbd_hc_probe_timer(iu_tq_t *tq, void *arg) { ilbd_hc_srv_t *srv = (ilbd_hc_srv_t *)arg; /* * If starting the probe fails, just pretend that the timeout has * extended. */ if (!ilbd_run_probe(srv)) { /* * If we cannot restart the timer, the only thing we can do * is to disable this server. Hopefully the sys admin will * notice this and enable this server again later. */ if (ilbd_hc_restart_timer(srv->shc_hc, srv) != ILB_STATUS_OK) { logerr("%s: cannot restart timer: rule %s server %s, " "disabling it", __func__, srv->shc_hc_rule->hcr_rule->irl_name, srv->shc_sg_srv->sgs_srvID); ilbd_mark_server_disabled(srv); } return; } /* * Similar to above, if kill timer cannot be started, disable the * server. */ if ((srv->shc_tid = iu_schedule_timer(ilbd_hc_timer_q, srv->shc_hc->ihc_timeout, ilbd_hc_kill_timer, srv)) == -1) { logerr("%s: cannot start kill timer: rule %s server %s, " "disabling it", __func__, srv->shc_hc_rule->hcr_rule->irl_name, srv->shc_sg_srv->sgs_srvID); ilbd_mark_server_disabled(srv); } hc_timer_restarted = B_TRUE; } /* Restart the periodic timer for a given server. */ static ilb_status_t ilbd_hc_restart_timer(ilbd_hc_t *hc, ilbd_hc_srv_t *srv) { int timeout; /* Don't allow the timeout interval to be less than 1s */ timeout = MAX((hc->ihc_interval >> 1) + (gethrtime() % (hc->ihc_interval + 1)), 1); /* * If the probe is actually a ping probe, there is no need to * do default pinging. Just skip the step. */ if (hc->ihc_def_ping && hc->ihc_test_type != ILBD_HC_PING) srv->shc_state = ilbd_hc_def_pinging; else srv->shc_state = ilbd_hc_probing; srv->shc_tid = iu_schedule_timer(ilbd_hc_timer_q, timeout, ilbd_hc_probe_timer, srv); if (srv->shc_tid == -1) return (ILB_STATUS_TIMER); srv->shc_lasttime = time(NULL); srv->shc_nexttime = time(NULL) + timeout; hc_timer_restarted = B_TRUE; return (ILB_STATUS_OK); } /* Helper routine to associate a server with its hc object. */ static ilb_status_t ilbd_hc_srv_add(ilbd_hc_t *hc, ilbd_hc_rule_t *hc_rule, const ilb_sg_srv_t *srv, int ev_port) { ilbd_hc_srv_t *new_srv; ilb_status_t ret; if ((new_srv = calloc(1, sizeof (ilbd_hc_srv_t))) == NULL) return (ILB_STATUS_ENOMEM); new_srv->shc_hc = hc; new_srv->shc_hc_rule = hc_rule; new_srv->shc_sg_srv = srv; new_srv->shc_ev_port = ev_port; new_srv->shc_tid = -1; new_srv->shc_nexttime = time(NULL); new_srv->shc_lasttime = new_srv->shc_nexttime; if ((hc_rule->hcr_rule->irl_flags & ILB_FLAGS_RULE_ENABLED) && ILB_IS_SRV_ENABLED(srv->sgs_flags)) { new_srv->shc_status = ILB_HCS_UNINIT; ret = ilbd_hc_restart_timer(hc, new_srv); if (ret != ILB_STATUS_OK) { free(new_srv); return (ret); } } else { new_srv->shc_status = ILB_HCS_DISABLED; } list_insert_tail(&hc_rule->hcr_servers, new_srv); return (ILB_STATUS_OK); } /* Handy macro to cancel a server's timer. */ #define HC_CANCEL_TIMER(srv) \ { \ void *arg; \ int ret; \ if ((srv)->shc_tid != -1) { \ ret = iu_cancel_timer(ilbd_hc_timer_q, (srv)->shc_tid, &arg); \ (srv)->shc_tid = -1; \ assert(ret == 1); \ assert(arg == (srv)); \ } \ hc_timer_restarted = B_TRUE; \ } /* Helper routine to dissociate a server from its hc object. */ static ilb_status_t ilbd_hc_srv_rem(ilbd_hc_rule_t *hc_rule, const ilb_sg_srv_t *srv) { ilbd_hc_srv_t *tmp_srv; for (tmp_srv = list_head(&hc_rule->hcr_servers); tmp_srv != NULL; tmp_srv = list_next(&hc_rule->hcr_servers, tmp_srv)) { if (tmp_srv->shc_sg_srv == srv) { list_remove(&hc_rule->hcr_servers, tmp_srv); HC_CANCEL_TIMER(tmp_srv); if (tmp_srv->shc_child_pid != 0) ilbd_hc_kill_probe(tmp_srv); free(tmp_srv); return (ILB_STATUS_OK); } } return (ILB_STATUS_ENOENT); } /* Helper routine to dissociate all servers of a rule from its hc object. */ static void ilbd_hc_srv_rem_all(ilbd_hc_rule_t *hc_rule) { ilbd_hc_srv_t *srv; while ((srv = list_remove_head(&hc_rule->hcr_servers)) != NULL) { HC_CANCEL_TIMER(srv); if (srv->shc_child_pid != 0) ilbd_hc_kill_probe(srv); free(srv); } } /* Associate a rule with its hc object. */ ilb_status_t ilbd_hc_associate_rule(const ilbd_rule_t *rule, int ev_port) { ilbd_hc_t *hc; ilbd_hc_rule_t *hc_rule; ilb_status_t ret; ilbd_sg_t *sg; ilbd_srv_t *ilbd_srv; /* The rule is assumed to be initialized appropriately. */ if ((hc = ilbd_get_hc(rule->irl_hcname)) == NULL) { logdebug("ilbd_hc_associate_rule: healthcheck %s does not " "exist", rule->irl_hcname); return (ILB_STATUS_ENOHCINFO); } if ((hc->ihc_test_type == ILBD_HC_TCP && rule->irl_proto != IPPROTO_TCP) || (hc->ihc_test_type == ILBD_HC_UDP && rule->irl_proto != IPPROTO_UDP)) { return (ILB_STATUS_RULE_HC_MISMATCH); } if ((hc_rule = calloc(1, sizeof (ilbd_hc_rule_t))) == NULL) { logdebug("ilbd_hc_associate_rule: out of memory"); return (ILB_STATUS_ENOMEM); } hc_rule->hcr_rule = rule; list_create(&hc_rule->hcr_servers, sizeof (ilbd_hc_srv_t), offsetof(ilbd_hc_srv_t, shc_srv_link)); /* Add all the servers. */ sg = rule->irl_sg; for (ilbd_srv = list_head(&sg->isg_srvlist); ilbd_srv != NULL; ilbd_srv = list_next(&sg->isg_srvlist, ilbd_srv)) { if ((ret = ilbd_hc_srv_add(hc, hc_rule, &ilbd_srv->isv_srv, ev_port)) != ILB_STATUS_OK) { /* Remove all previously added servers */ ilbd_hc_srv_rem_all(hc_rule); list_destroy(&hc_rule->hcr_servers); free(hc_rule); return (ret); } } list_insert_tail(&hc->ihc_rules, hc_rule); hc->ihc_rule_cnt++; return (ILB_STATUS_OK); } /* Dissociate a rule from its hc object. */ ilb_status_t ilbd_hc_dissociate_rule(const ilbd_rule_t *rule) { ilbd_hc_t *hc; ilbd_hc_rule_t *hc_rule; /* The rule is assumed to be initialized appropriately. */ if ((hc = ilbd_get_hc(rule->irl_hcname)) == NULL) { logdebug("ilbd_hc_dissociate_rule: healthcheck %s does not " "exist", rule->irl_hcname); return (ILB_STATUS_ENOENT); } for (hc_rule = list_head(&hc->ihc_rules); hc_rule != NULL; hc_rule = list_next(&hc->ihc_rules, hc_rule)) { if (hc_rule->hcr_rule == rule) break; } if (hc_rule == NULL) { logdebug("ilbd_hc_dissociate_rule: rule %s is not associated " "with healtcheck %s", rule->irl_hcname, hc->ihc_name); return (ILB_STATUS_ENOENT); } ilbd_hc_srv_rem_all(hc_rule); list_remove(&hc->ihc_rules, hc_rule); hc->ihc_rule_cnt--; list_destroy(&hc_rule->hcr_servers); free(hc_rule); return (ILB_STATUS_OK); } /* * Given a hc object name and a rule, check to see if the rule is associated * with the hc object. If it is, the hc object is returned in **hc and the * ilbd_hc_rule_t is returned in **hc_rule. */ static boolean_t ilbd_hc_check_rule(const char *hc_name, const ilbd_rule_t *rule, ilbd_hc_t **hc, ilbd_hc_rule_t **hc_rule) { ilbd_hc_t *tmp_hc; ilbd_hc_rule_t *tmp_hc_rule; if ((tmp_hc = ilbd_get_hc(hc_name)) == NULL) return (B_FALSE); for (tmp_hc_rule = list_head(&tmp_hc->ihc_rules); tmp_hc_rule != NULL; tmp_hc_rule = list_next(&tmp_hc->ihc_rules, tmp_hc_rule)) { if (tmp_hc_rule->hcr_rule == rule) { *hc = tmp_hc; *hc_rule = tmp_hc_rule; return (B_TRUE); } } return (B_FALSE); } /* Associate a server with its hc object. */ ilb_status_t ilbd_hc_add_server(const ilbd_rule_t *rule, const ilb_sg_srv_t *srv, int ev_port) { ilbd_hc_t *hc; ilbd_hc_rule_t *hc_rule; if (!ilbd_hc_check_rule(rule->irl_hcname, rule, &hc, &hc_rule)) return (ILB_STATUS_ENOENT); return (ilbd_hc_srv_add(hc, hc_rule, srv, ev_port)); } /* Dissociate a server from its hc object. */ ilb_status_t ilbd_hc_del_server(const ilbd_rule_t *rule, const ilb_sg_srv_t *srv) { ilbd_hc_t *hc; ilbd_hc_rule_t *hc_rule; if (!ilbd_hc_check_rule(rule->irl_hcname, rule, &hc, &hc_rule)) return (ILB_STATUS_ENOENT); return (ilbd_hc_srv_rem(hc_rule, srv)); } /* Helper routine to enable/disable a server's hc probe. */ static ilb_status_t ilbd_hc_toggle_server(const ilbd_rule_t *rule, const ilb_sg_srv_t *srv, boolean_t enable) { ilbd_hc_t *hc; ilbd_hc_rule_t *hc_rule; ilbd_hc_srv_t *tmp_srv; ilb_status_t ret; if (!ilbd_hc_check_rule(rule->irl_hcname, rule, &hc, &hc_rule)) return (ILB_STATUS_ENOENT); for (tmp_srv = list_head(&hc_rule->hcr_servers); tmp_srv != NULL; tmp_srv = list_next(&hc_rule->hcr_servers, tmp_srv)) { if (tmp_srv->shc_sg_srv != srv) { continue; } if (enable) { if (tmp_srv->shc_status == ILB_HCS_DISABLED) { ret = ilbd_hc_restart_timer(hc, tmp_srv); if (ret != ILB_STATUS_OK) { logerr("%s: cannot start timers for " "rule %s server %s", __func__, rule->irl_name, tmp_srv->shc_sg_srv->sgs_srvID); return (ret); } /* Start from fresh... */ tmp_srv->shc_status = ILB_HCS_UNINIT; tmp_srv->shc_rtt = 0; tmp_srv->shc_fail_cnt = 0; } } else { if (tmp_srv->shc_status != ILB_HCS_DISABLED) { tmp_srv->shc_status = ILB_HCS_DISABLED; HC_CANCEL_TIMER(tmp_srv); if (tmp_srv->shc_child_pid != 0) ilbd_hc_kill_probe(tmp_srv); } } return (ILB_STATUS_OK); } return (ILB_STATUS_ENOENT); } ilb_status_t ilbd_hc_enable_server(const ilbd_rule_t *rule, const ilb_sg_srv_t *srv) { return (ilbd_hc_toggle_server(rule, srv, B_TRUE)); } ilb_status_t ilbd_hc_disable_server(const ilbd_rule_t *rule, const ilb_sg_srv_t *srv) { return (ilbd_hc_toggle_server(rule, srv, B_FALSE)); } /* * Helper routine to enable/disable a rule's hc probe (including all its * servers). */ static ilb_status_t ilbd_hc_toggle_rule(const ilbd_rule_t *rule, boolean_t enable) { ilbd_hc_t *hc; ilbd_hc_rule_t *hc_rule; ilbd_hc_srv_t *tmp_srv; int ret; if (!ilbd_hc_check_rule(rule->irl_hcname, rule, &hc, &hc_rule)) return (ILB_STATUS_ENOENT); for (tmp_srv = list_head(&hc_rule->hcr_servers); tmp_srv != NULL; tmp_srv = list_next(&hc_rule->hcr_servers, tmp_srv)) { if (enable) { /* * If the server is disabled in the rule, do not * restart its timer. */ if (tmp_srv->shc_status == ILB_HCS_DISABLED && ILB_IS_SRV_ENABLED( tmp_srv->shc_sg_srv->sgs_flags)) { ret = ilbd_hc_restart_timer(hc, tmp_srv); if (ret != ILB_STATUS_OK) { logerr("%s: cannot start timers for " "rule %s server %s", __func__, rule->irl_name, tmp_srv->shc_sg_srv->sgs_srvID); goto rollback; } else { /* Start from fresh... */ tmp_srv->shc_status = ILB_HCS_UNINIT; tmp_srv->shc_rtt = 0; tmp_srv->shc_fail_cnt = 0; } } } else { if (tmp_srv->shc_status != ILB_HCS_DISABLED) { HC_CANCEL_TIMER(tmp_srv); tmp_srv->shc_status = ILB_HCS_DISABLED; if (tmp_srv->shc_child_pid != 0) ilbd_hc_kill_probe(tmp_srv); } } } return (ILB_STATUS_OK); rollback: enable = !enable; for (tmp_srv = list_prev(&hc_rule->hcr_servers, tmp_srv); tmp_srv != NULL; tmp_srv = list_prev(&hc_rule->hcr_servers, tmp_srv)) { if (enable) { if (tmp_srv->shc_status == ILB_HCS_DISABLED && ILB_IS_SRV_ENABLED( tmp_srv->shc_sg_srv->sgs_flags)) { (void) ilbd_hc_restart_timer(hc, tmp_srv); tmp_srv->shc_status = ILB_HCS_UNINIT; tmp_srv->shc_rtt = 0; tmp_srv->shc_fail_cnt = 0; } } else { if (tmp_srv->shc_status != ILB_HCS_DISABLED) { HC_CANCEL_TIMER(tmp_srv); tmp_srv->shc_status = ILB_HCS_DISABLED; if (tmp_srv->shc_child_pid != 0) ilbd_hc_kill_probe(tmp_srv); } } } return (ret); } ilb_status_t ilbd_hc_enable_rule(const ilbd_rule_t *rule) { return (ilbd_hc_toggle_rule(rule, B_TRUE)); } ilb_status_t ilbd_hc_disable_rule(const ilbd_rule_t *rule) { return (ilbd_hc_toggle_rule(rule, B_FALSE)); } static const char * topo_2_str(ilb_topo_t topo) { switch (topo) { case ILB_TOPO_DSR: return ("DSR"); case ILB_TOPO_NAT: return ("NAT"); case ILB_TOPO_HALF_NAT: return ("HALF_NAT"); default: /* Should not happen. */ logerr("%s: unknown topology", __func__); break; } return (""); } /* * Create the argument list to be passed to a hc probe command. * The passed in argv is assumed to have HC_PROBE_ARGC elements. */ static boolean_t create_argv(ilbd_hc_srv_t *srv, char *argv[]) { char buf[INET6_ADDRSTRLEN]; ilbd_rule_t const *rule; ilb_sg_srv_t const *sg_srv; struct in_addr v4_addr; in_port_t port; int i; rule = srv->shc_hc_rule->hcr_rule; sg_srv = srv->shc_sg_srv; if (srv->shc_state == ilbd_hc_def_pinging) { if ((argv[0] = strdup(ILB_PROBE_PING)) == NULL) return (B_FALSE); } else { switch (srv->shc_hc->ihc_test_type) { case ILBD_HC_USER: if ((argv[0] = strdup(srv->shc_hc->ihc_test)) == NULL) return (B_FALSE); break; case ILBD_HC_TCP: case ILBD_HC_UDP: if ((argv[0] = strdup(ILB_PROBE_PROTO)) == NULL) { return (B_FALSE); } break; case ILBD_HC_PING: if ((argv[0] = strdup(ILB_PROBE_PING)) == NULL) { return (B_FALSE); } break; } } /* * argv[1] is the VIP. * * Right now, the VIP and the backend server addresses should be * in the same IP address family. Here we don't do that in case * this assumption is changed in future. */ if (IN6_IS_ADDR_V4MAPPED(&rule->irl_vip)) { IN6_V4MAPPED_TO_INADDR(&rule->irl_vip, &v4_addr); if (inet_ntop(AF_INET, &v4_addr, buf, sizeof (buf)) == NULL) goto cleanup; } else { if (inet_ntop(AF_INET6, &rule->irl_vip, buf, sizeof (buf)) == NULL) { goto cleanup; } } if ((argv[1] = strdup(buf)) == NULL) goto cleanup; /* * argv[2] is the backend server address. */ if (IN6_IS_ADDR_V4MAPPED(&sg_srv->sgs_addr)) { IN6_V4MAPPED_TO_INADDR(&sg_srv->sgs_addr, &v4_addr); if (inet_ntop(AF_INET, &v4_addr, buf, sizeof (buf)) == NULL) goto cleanup; } else { if (inet_ntop(AF_INET6, &sg_srv->sgs_addr, buf, sizeof (buf)) == NULL) { goto cleanup; } } if ((argv[2] = strdup(buf)) == NULL) goto cleanup; /* * argv[3] is the transport protocol used in the rule. */ switch (rule->irl_proto) { case IPPROTO_TCP: argv[3] = strdup("TCP"); break; case IPPROTO_UDP: argv[3] = strdup("UDP"); break; default: logerr("%s: unknown protocol", __func__); goto cleanup; } if (argv[3] == NULL) goto cleanup; /* * argv[4] is the load balance mode, DSR, NAT, HALF-NAT. */ if ((argv[4] = strdup(topo_2_str(rule->irl_topo))) == NULL) goto cleanup; /* * argv[5] is the port range. Right now, there should only be 1 port. */ switch (rule->irl_hcpflag) { case ILB_HCI_PROBE_FIX: port = ntohs(rule->irl_hcport); break; case ILB_HCI_PROBE_ANY: { in_port_t min, max; if (ntohs(sg_srv->sgs_minport) == 0) { min = ntohs(rule->irl_minport); max = ntohs(rule->irl_maxport); } else { min = ntohs(sg_srv->sgs_minport); max = ntohs(sg_srv->sgs_maxport); } if (max > min) port = min + gethrtime() % (max - min + 1); else port = min; break; } default: logerr("%s: unknown HC flag", __func__); goto cleanup; } (void) sprintf(buf, "%d", port); if ((argv[5] = strdup(buf)) == NULL) goto cleanup; /* * argv[6] is the probe timeout. */ (void) sprintf(buf, "%d", srv->shc_hc->ihc_timeout); if ((argv[6] = strdup(buf)) == NULL) goto cleanup; argv[7] = NULL; return (B_TRUE); cleanup: for (i = 0; i < HC_PROBE_ARGC; i++) { if (argv[i] != NULL) free(argv[i]); } return (B_FALSE); } static void destroy_argv(char *argv[]) { int i; for (i = 0; argv[i] != NULL; i++) free(argv[i]); } /* Spawn a process to run the hc probe on the given server. */ static boolean_t ilbd_run_probe(ilbd_hc_srv_t *srv) { posix_spawn_file_actions_t fd_actions; boolean_t init_fd_actions = B_FALSE; posix_spawnattr_t attr; boolean_t init_attr = B_FALSE; sigset_t child_sigset; int fds[2]; int fdflags; pid_t pid; char *child_argv[HC_PROBE_ARGC]; ilbd_hc_probe_event_t *probe_ev; char *probe_name; bzero(child_argv, HC_PROBE_ARGC * sizeof (char *)); if ((probe_ev = calloc(1, sizeof (*probe_ev))) == NULL) { logdebug("ilbd_run_probe: calloc"); return (B_FALSE); } /* Set up a pipe to get output from probe command. */ if (pipe(fds) < 0) { logdebug("ilbd_run_probe: cannot create pipe"); free(probe_ev); return (B_FALSE); } /* Set our side of the pipe to be non-blocking */ if ((fdflags = fcntl(fds[0], F_GETFL, 0)) == -1) { logdebug("ilbd_run_probe: fcntl(F_GETFL)"); goto cleanup; } if (fcntl(fds[0], F_SETFL, fdflags | O_NONBLOCK) == -1) { logdebug("ilbd_run_probe: fcntl(F_SETFL)"); goto cleanup; } if (posix_spawn_file_actions_init(&fd_actions) != 0) { logdebug("ilbd_run_probe: posix_spawn_file_actions_init"); goto cleanup; } init_fd_actions = B_TRUE; if (posix_spawnattr_init(&attr) != 0) { logdebug("ilbd_run_probe: posix_spawnattr_init"); goto cleanup; } init_attr = B_TRUE; if (posix_spawn_file_actions_addclose(&fd_actions, fds[0]) != 0) { logdebug("ilbd_run_probe: posix_spawn_file_actions_addclose"); goto cleanup; } if (posix_spawn_file_actions_adddup2(&fd_actions, fds[1], STDOUT_FILENO) != 0) { logdebug("ilbd_run_probe: posix_spawn_file_actions_dup2"); goto cleanup; } if (posix_spawn_file_actions_addclose(&fd_actions, fds[1]) != 0) { logdebug("ilbd_run_probe: posix_spawn_file_actions_addclose"); goto cleanup; } /* Reset all signal handling of the child to default. */ (void) sigfillset(&child_sigset); if (posix_spawnattr_setsigdefault(&attr, &child_sigset) != 0) { logdebug("ilbd_run_probe: posix_spawnattr_setsigdefault"); goto cleanup; } /* Don't want SIGCHLD. */ if (posix_spawnattr_setflags(&attr, POSIX_SPAWN_NOSIGCHLD_NP| POSIX_SPAWN_SETSIGDEF) != 0) { logdebug("ilbd_run_probe: posix_spawnattr_setflags"); goto cleanup; } if (!create_argv(srv, child_argv)) { logdebug("ilbd_run_probe: create_argv"); goto cleanup; } /* * If we are doing default pinging or not using a user supplied * probe, we should execute our standard supplied probe. The * supplied probe command handles all types of probes. And the * type used depends on argv[0], as filled in by create_argv(). */ if (srv->shc_state == ilbd_hc_def_pinging || srv->shc_hc->ihc_test_type != ILBD_HC_USER) { probe_name = ILB_PROBE_PROTO; } else { probe_name = srv->shc_hc->ihc_test; } if (posix_spawn(&pid, probe_name, &fd_actions, &attr, child_argv, NULL) != 0) { logerr("%s: posix_spawn: %s for server %s: %s", __func__, srv->shc_hc->ihc_test, srv->shc_sg_srv->sgs_srvID, strerror(errno)); goto cleanup; } (void) close(fds[1]); srv->shc_child_pid = pid; srv->shc_child_fd = fds[0]; srv->shc_ev = probe_ev; probe_ev->ihp_ev = ILBD_EVENT_PROBE; probe_ev->ihp_srv = srv; probe_ev->ihp_pid = pid; if (port_associate(srv->shc_ev_port, PORT_SOURCE_FD, fds[0], POLLRDNORM, probe_ev) != 0) { /* * Need to kill the child. It will free the srv->shc_ev, * which is probe_ev. So set probe_ev to NULL. */ ilbd_hc_kill_probe(srv); probe_ev = NULL; goto cleanup; } destroy_argv(child_argv); (void) posix_spawn_file_actions_destroy(&fd_actions); (void) posix_spawnattr_destroy(&attr); return (B_TRUE); cleanup: destroy_argv(child_argv); if (init_fd_actions == B_TRUE) (void) posix_spawn_file_actions_destroy(&fd_actions); if (init_attr == B_TRUE) (void) posix_spawnattr_destroy(&attr); (void) close(fds[0]); (void) close(fds[1]); if (probe_ev != NULL) free(probe_ev); return (B_FALSE); } /* * Called by ild_hc_probe_return() to re-associate the fd to a child to * the event port. */ static void reassociate_port(int ev_port, int fd, ilbd_hc_probe_event_t *ev) { if (port_associate(ev_port, PORT_SOURCE_FD, fd, POLLRDNORM, ev) != 0) { /* * If we cannot reassociate with the port, the only * thing we can do now is to kill the child and * do a blocking wait here... */ logdebug("%s: port_associate: %s", __func__, strerror(errno)); if (kill(ev->ihp_pid, SIGKILL) != 0) logerr("%s: kill: %s", __func__, strerror(errno)); if (waitpid(ev->ihp_pid, NULL, 0) != ev->ihp_pid) logdebug("%s: waitpid: %s", __func__, strerror(errno)); free(ev); } } /* * To handle a child probe process hanging up. */ static void ilbd_hc_child_hup(int ev_port, int fd, ilbd_hc_probe_event_t *ev) { ilbd_hc_srv_t *srv; pid_t ret_pid; int ret; srv = ev->ihp_srv; if (!ev->ihp_done) { /* ilbd does not care about this process anymore ... */ ev->ihp_done = B_TRUE; srv->shc_ev = NULL; srv->shc_child_pid = 0; HC_CANCEL_TIMER(srv); ilbd_set_fail_state(srv); } ret_pid = waitpid(ev->ihp_pid, &ret, WNOHANG); switch (ret_pid) { case -1: logperror("ilbd_hc_child_hup: waitpid"); /* FALLTHROUGH */ case 0: /* The child has not completed the exit. Wait again. */ reassociate_port(ev_port, fd, ev); break; default: /* Right now, we just ignore the exit status. */ if (WIFEXITED(ret)) ret = WEXITSTATUS(ret); (void) close(fd); free(ev); } } /* * To read the output of a child probe process. */ static void ilbd_hc_child_data(int fd, ilbd_hc_probe_event_t *ev) { ilbd_hc_srv_t *srv; char buf[HC_MAX_PROBE_OUTPUT]; int ret; int64_t rtt; srv = ev->ihp_srv; bzero(buf, HC_MAX_PROBE_OUTPUT); ret = read(fd, buf, HC_MAX_PROBE_OUTPUT - 1); /* Should not happen since event port should have caught this. */ assert(ret > 0); /* * We expect the probe command to print out the RTT only. But * the command may misbehave and print out more than what we intend to * read in. So need to do this check below to "flush" out all the * output from the command. */ if (!ev->ihp_done) { ev->ihp_done = B_TRUE; /* We don't need to know about this event anymore. */ srv->shc_ev = NULL; srv->shc_child_pid = 0; HC_CANCEL_TIMER(srv); } else { return; } rtt = strtoll(buf, NULL, 10); /* * -1 means the server is dead or the probe somehow fails. Treat * them both as server is dead. */ if (rtt == -1) { ilbd_set_fail_state(srv); return; } else if (rtt > 0) { /* If the returned RTT value is not valid, just ignore it. */ if (rtt > 0 && rtt <= UINT_MAX) { /* Set rtt to be the simple smoothed average. */ if (srv->shc_rtt == 0) { srv->shc_rtt = rtt; } else { srv->shc_rtt = 3 * ((srv)->shc_rtt >> 2) + (rtt >> 2); } } } switch (srv->shc_state) { case ilbd_hc_def_pinging: srv->shc_state = ilbd_hc_probing; /* Ping is OK, now start the probe. */ ilbd_hc_probe_timer(ilbd_hc_timer_q, srv); break; case ilbd_hc_probing: srv->shc_fail_cnt = 0; /* Server is dead before, re-enable it. */ if (srv->shc_status == ILB_HCS_UNREACH || srv->shc_status == ILB_HCS_DEAD) { /* * If enabling the server in kernel fails now, * hopefully when the timer fires again later, the * enabling can be done. */ if (ilbd_k_Xable_server(&srv->shc_sg_srv->sgs_addr, srv->shc_hc_rule->hcr_rule->irl_name, stat_declare_srv_alive) != ILB_STATUS_OK) { logerr("%s: cannot enable server in kernel: " " rule %s server %s", __func__, srv->shc_hc_rule->hcr_rule->irl_name, srv->shc_sg_srv->sgs_srvID); } else { srv->shc_status = ILB_HCS_ALIVE; } } else { srv->shc_status = ILB_HCS_ALIVE; } if (ilbd_hc_restart_timer(srv->shc_hc, srv) != ILB_STATUS_OK) { logerr("%s: cannot restart timer: rule %s server %s", __func__, srv->shc_hc_rule->hcr_rule->irl_name, srv->shc_sg_srv->sgs_srvID); ilbd_mark_server_disabled(srv); } break; default: logdebug("%s: unknown state", __func__); break; } } /* * Handle the return event of a child probe fd. */ void ilbd_hc_probe_return(int ev_port, int fd, int port_events, ilbd_hc_probe_event_t *ev) { /* * Note that there can be more than one events delivered to us at * the same time. So we need to check them individually. */ if (port_events & POLLRDNORM) ilbd_hc_child_data(fd, ev); if (port_events & (POLLHUP|POLLERR)) { ilbd_hc_child_hup(ev_port, fd, ev); return; } /* * Re-associate the fd with the port so that when the child * exits, we can reap the status. */ reassociate_port(ev_port, fd, ev); } /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* * 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 * program that requires a larger privilege set, they will have * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libilb_impl.h" #include "ilbd.h" /* * NOTE: The following needs to be kept up to date. */ #define ILBD_VERSION "1.0" #define ILBD_COPYRIGHT \ "Copyright (c) 2005, 2010, Oracle and/or its affiliates. " \ "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 uint32_t reply_buf[ILBD_MSG_SIZE / sizeof (uint32_t)]; static void ilbd_free_cli(ilbd_client_t *cli) { (void) close(cli->cli_sd); if (cli->cli_cmd == ILBD_SHOW_NAT) ilbd_show_nat_cleanup(); if (cli->cli_cmd == ILBD_SHOW_PERSIST) ilbd_show_sticky_cleanup(); if (cli->cli_saved_reply != NULL) free(cli->cli_saved_reply); if (cli->cli_peer_ucredp != NULL) ucred_free(cli->cli_peer_ucredp); free(cli->cli_pw_buf); free(cli); } static void ilbd_reset_kernel_state(void) { ilb_status_t rc; ilb_name_cmd_t kcmd; kcmd.cmd = ILB_DESTROY_RULE; kcmd.flags = ILB_RULE_ALLRULES; kcmd.name[0] = '\0'; rc = do_ioctl(&kcmd, 0); if (rc != ILB_STATUS_OK) logdebug("ilbd_reset_kernel_state: do_ioctl failed: %s", strerror(errno)); } /* Signal handler to do clean up. */ /* ARGSUSED */ static void ilbd_cleanup(int sig) { (void) remove(SOCKET_PATH); ilbd_reset_kernel_state(); 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 ilbd_create_client_socket(void) { int s; mode_t omask; struct sockaddr_un sa; int sobufsz; s = socket(PF_UNIX, SOCK_SEQPACKET, 0); if (s == -1) { perror("ilbd_create_client_socket: socket to" " client failed"); exit(errno); } if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) { perror("ilbd_create_client_socket: fcntl(FD_CLOEXEC)"); exit(errno); } sobufsz = ILBD_MSG_SIZE; if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sobufsz, sizeof (sobufsz)) != 0) { perror("ilbd_creat_client_socket: setsockopt(SO_SNDBUF) " "failed"); exit(errno); } if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sobufsz, sizeof (sobufsz)) != 0) { perror("ilbd_creat_client_socket: setsockopt(SO_RCVBUF) " "failed"); exit(errno); } /* * 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. */ omask = umask(0); /* just in case we didn't clean up properly after last exit */ (void) remove(SOCKET_PATH); bzero(&sa, sizeof (sa)); sa.sun_family = AF_UNIX; (void) strlcpy(sa.sun_path, SOCKET_PATH, sizeof (sa.sun_path)); if (bind(s, (struct sockaddr *)&sa, sizeof (sa)) != 0) { perror("ilbd_create_client_socket(): bind to client" " socket failed"); exit(errno); } /* re-instate old umask */ (void) umask(omask); #define QLEN 16 if (listen(s, QLEN) != 0) { perror("ilbd_create_client_socket: listen to client" " socket failed"); exit(errno); } (void) signal(SIGHUP, SIG_IGN); (void) signal(SIGPIPE, SIG_IGN); (void) signal(SIGSTOP, SIG_IGN); (void) signal(SIGTSTP, SIG_IGN); (void) signal(SIGTTIN, SIG_IGN); (void) signal(SIGTTOU, SIG_IGN); (void) signal(SIGINT, ilbd_cleanup); (void) signal(SIGTERM, ilbd_cleanup); (void) signal(SIGQUIT, ilbd_cleanup); 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 ilbd_cmd_size(const ilb_comm_t *ic) { size_t cmd_sz; cmd_sz = sizeof (*ic); switch (ic->ic_cmd) { 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: case ILBD_REM_SERVER_FROM_GROUP: cmd_sz += sizeof (ilb_sg_info_t); break; case ILBD_SRV_ADDR2ID: case ILBD_SRV_ID2ADDR: cmd_sz += sizeof (ilb_sg_info_t) + sizeof (ilb_sg_srv_t); 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 ilbd_check_req_size(ilb_comm_t *ic, size_t ic_sz) { ilb_status_t rc = ILB_STATUS_OK; ilb_sg_info_t *sg_info; ilbd_namelist_t *nlist; switch (ic->ic_cmd) { case ILBD_CREATE_SERVERGROUP: case ILBD_ENABLE_SERVER: case ILBD_DISABLE_SERVER: case ILBD_ADD_SERVER_TO_GROUP: case ILBD_REM_SERVER_FROM_GROUP: sg_info = (ilb_sg_info_t *)&ic->ic_data; if (ic_sz < ilbd_cmd_size(ic) + sg_info->sg_srvcount * sizeof (ilb_sg_srv_t)) { rc = ILB_STATUS_EINVAL; } break; case ILBD_ENABLE_RULE: case ILBD_DISABLE_RULE: case ILBD_DESTROY_RULE: nlist = (ilbd_namelist_t *)&ic->ic_data; if (ic_sz < ilbd_cmd_size(ic) + nlist->ilbl_count * sizeof (ilbd_name_t)) { rc = ILB_STATUS_EINVAL; } break; } return (rc); } /* * this function *relies* on a complete message/data struct * 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 consume_common_struct(ilb_comm_t *ic, size_t ic_sz, ilbd_client_t *cli, int ev_port) { ilb_status_t rc; struct passwd *ps; size_t rbufsz; ssize_t ret; boolean_t standard_reply = B_TRUE; ilbd_name_t name; /* * cli_ev must be overridden during handling of individual commands, * if there's a special need; otherwise, leave this for * the "default" case */ cli->cli_ev = ILBD_EVENT_REQ; ps = &cli->cli_pw; rbufsz = ILBD_MSG_SIZE; /* Sanity check on the size of the static part of a request. */ if (ic_sz < ilbd_cmd_size(ic)) { rc = ILB_STATUS_EINVAL; goto out; } switch (ic->ic_cmd) { case ILBD_CREATE_SERVERGROUP: { ilb_sg_info_t sg_info; /* * 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(). */ (void) strlcpy(sg_info.sg_name, (char *)&(ic->ic_data), sizeof (sg_info.sg_name)); rc = ilbd_create_sg(&sg_info, ev_port, ps, cli->cli_peer_ucredp); break; } case ILBD_DESTROY_SERVERGROUP: (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name)); rc = ilbd_destroy_sg(name, ps, cli->cli_peer_ucredp); break; case ILBD_ADD_SERVER_TO_GROUP: if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK) break; rc = ilbd_add_server_to_group((ilb_sg_info_t *)&ic->ic_data, ev_port, ps, cli->cli_peer_ucredp); break; case ILBD_REM_SERVER_FROM_GROUP: if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK) break; rc = ilbd_rem_server_from_group((ilb_sg_info_t *)&ic->ic_data, ev_port, ps, cli->cli_peer_ucredp); break; case ILBD_ENABLE_SERVER: if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK) break; rc = ilbd_enable_server((ilb_sg_info_t *)&ic->ic_data, ps, cli->cli_peer_ucredp); break; case ILBD_DISABLE_SERVER: if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK) break; rc = ilbd_disable_server((ilb_sg_info_t *)&ic->ic_data, ps, cli->cli_peer_ucredp); break; case ILBD_SRV_ADDR2ID: rc = ilbd_address_to_srvID((ilb_sg_info_t *)&ic->ic_data, reply_buf, &rbufsz); if (rc == ILB_STATUS_OK) standard_reply = B_FALSE; break; case ILBD_SRV_ID2ADDR: rc = ilbd_srvID_to_address((ilb_sg_info_t *)&ic->ic_data, reply_buf, &rbufsz); if (rc == ILB_STATUS_OK) standard_reply = B_FALSE; break; case ILBD_RETRIEVE_SG_HOSTS: (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name)); rc = ilbd_retrieve_sg_hosts(name, reply_buf, &rbufsz); if (rc == ILB_STATUS_OK) standard_reply = B_FALSE; break; case ILBD_RETRIEVE_SG_NAMES: case ILBD_RETRIEVE_RULE_NAMES: case ILBD_RETRIEVE_HC_NAMES: rc = ilbd_retrieve_names(ic->ic_cmd, reply_buf, &rbufsz); if (rc == ILB_STATUS_OK) standard_reply = B_FALSE; break; case ILBD_CREATE_RULE: rc = ilbd_create_rule((ilb_rule_info_t *)&ic->ic_data, ev_port, ps, cli->cli_peer_ucredp); break; case ILBD_DESTROY_RULE: /* Copy the name to ensure that name is NULL terminated. */ (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name)); rc = ilbd_destroy_rule(name, ps, cli->cli_peer_ucredp); break; case ILBD_ENABLE_RULE: (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name)); rc = ilbd_enable_rule(name, ps, cli->cli_peer_ucredp); break; case ILBD_DISABLE_RULE: (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name)); rc = ilbd_disable_rule(name, ps, cli->cli_peer_ucredp); break; case ILBD_RETRIEVE_RULE: (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name)); rc = ilbd_retrieve_rule(name, reply_buf, &rbufsz); if (rc == ILB_STATUS_OK) standard_reply = B_FALSE; break; case ILBD_CREATE_HC: rc = ilbd_create_hc((ilb_hc_info_t *)&ic->ic_data, ev_port, ps, cli->cli_peer_ucredp); break; case ILBD_DESTROY_HC: (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name)); rc = ilbd_destroy_hc(name, ps, cli->cli_peer_ucredp); break; case ILBD_GET_HC_INFO: (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name)); rc = ilbd_get_hc_info(name, reply_buf, &rbufsz); if (rc == ILB_STATUS_OK) standard_reply = B_FALSE; break; case ILBD_GET_HC_SRVS: (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name)); rc = ilbd_get_hc_srvs(name, reply_buf, &rbufsz); if (rc == ILB_STATUS_OK) standard_reply = B_FALSE; break; case ILBD_SHOW_NAT: rc = ilbd_show_nat(cli, ic, reply_buf, &rbufsz); if (rc == ILB_STATUS_OK) standard_reply = B_FALSE; break; case ILBD_SHOW_PERSIST: rc = ilbd_show_sticky(cli, ic, reply_buf, &rbufsz); if (rc == ILB_STATUS_OK) standard_reply = B_FALSE; break; default: logdebug("consume_common_struct: unknown command"); rc = ILB_STATUS_INVAL_CMD; break; } out: /* * The message exchange is always in pairs, request/response. If * 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) ilbd_reply_ok(reply_buf, &rbufsz); else ilbd_reply_err(reply_buf, &rbufsz, rc); } if ((ret = send(cli->cli_sd, reply_buf, rbufsz, 0)) != rbufsz) { if (ret == -1) { if (errno != EWOULDBLOCK) { logdebug("consume_common_struct: send: %s", strerror(errno)); rc = ILB_STATUS_SEND; goto err_out; } /* * The reply is blocked, save the reply. handle_req() * will associate the event port for the re-send. */ assert(cli->cli_saved_reply == NULL); if ((cli->cli_saved_reply = malloc(rbufsz)) == NULL) { /* * 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"); rc = ILB_STATUS_SEND; goto err_out; } bcopy(reply_buf, cli->cli_saved_reply, rbufsz); cli->cli_saved_size = rbufsz; return (ILB_STATUS_EWOULDBLOCK); } } err_out: 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 new_req(int ev_port, int listener, void *ev_obj) { struct sockaddr sa; int sa_len; int new_sd; int sflags; ilbd_client_t *cli = NULL; int res; uid_t uid; sa_len = sizeof (sa); if ((new_sd = accept(listener, &sa, &sa_len)) == -1) { /* don't log if we're out of file descriptors */ if (errno != EINTR && errno != EMFILE) logperror("new_req: accept failed"); goto done; } /* Set the new socket to be non-blocking. */ if ((sflags = fcntl(new_sd, F_GETFL, 0)) == -1) { logperror("new_req: fcntl(F_GETFL)"); goto clean_up; } if (fcntl(new_sd, F_SETFL, sflags | O_NONBLOCK) == -1) { logperror("new_req: fcntl(F_SETFL)"); goto clean_up; } if (fcntl(new_sd, F_SETFD, FD_CLOEXEC) == -1) { logperror("new_req: fcntl(FD_CLOEXEC)"); goto clean_up; } if ((cli = calloc(1, sizeof (ilbd_client_t))) == NULL) { logerr("new_req: malloc(ilbd_client_t)"); goto clean_up; } res = getpeerucred(new_sd, &cli->cli_peer_ucredp); if (res == -1) { logperror("new_req: getpeerucred failed"); goto clean_up; } if ((uid = ucred_getruid(cli->cli_peer_ucredp)) == (uid_t)-1) { logperror("new_req: ucred_getruid failed"); goto clean_up; } cli->cli_pw_bufsz = (size_t)sysconf(_SC_GETPW_R_SIZE_MAX); if ((cli->cli_pw_buf = malloc(cli->cli_pw_bufsz)) == NULL) { logerr("new_req: malloc(cli_pw_buf)"); goto clean_up; } if (getpwuid_r(uid, &cli->cli_pw, cli->cli_pw_buf, cli->cli_pw_bufsz) == NULL) { logperror("new_req: invalid user"); goto clean_up; } cli->cli_ev = ILBD_EVENT_REQ; cli->cli_sd = new_sd; cli->cli_cmd = ILBD_BAD_CMD; cli->cli_saved_reply = NULL; cli->cli_saved_size = 0; if (port_associate(ev_port, PORT_SOURCE_FD, new_sd, POLLRDNORM, cli) == -1) { logperror("new_req: port_associate(cli) failed"); clean_up: if (cli != NULL) { if (cli->cli_peer_ucredp != NULL) ucred_free(cli->cli_peer_ucredp); free(cli->cli_pw_buf); free(cli); } (void) close(new_sd); } done: /* Re-associate the listener with the event port. */ if (port_associate(ev_port, PORT_SOURCE_FD, listener, POLLRDNORM, ev_obj) == -1) { logperror("new_req: port_associate(listener) failed"); exit(1); } } static void handle_req(int ev_port, ilbd_event_t event, ilbd_client_t *cli) { /* All request should be smaller than ILBD_MSG_SIZE */ union { ilb_comm_t ic; uint32_t buf[ILBD_MSG_SIZE / sizeof (uint32_t)]; } ic_u; int rc = ILB_STATUS_OK; 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. */ if (cli->cli_saved_reply != NULL) { logerr("handle_req: misbehaving client, more than one " "outstanding request"); rc = ILB_STATUS_INTERNAL; goto err_out; } /* * Our socket is message based so we should be able * to get the request in one single read. */ r = recv(cli->cli_sd, (void *)ic_u.buf, sizeof (ic_u.buf), 0); if (r < 0) { if (errno != EINTR) { logperror("handle_req: read failed"); rc = ILB_STATUS_READ; goto err_out; } /* * If interrupted, just re-associate the cli_sd * with the port. */ goto done; } cli->cli_cmd = ic_u.ic.ic_cmd; rc = consume_common_struct(&ic_u.ic, r, cli, ev_port); if (rc == ILB_STATUS_EWOULDBLOCK) goto blocked; /* Fatal error communicating with client, free it. */ if (rc == ILB_STATUS_SEND) goto err_out; } else { assert(event == ILBD_EVENT_REP_OK); assert(cli->cli_saved_reply != NULL); /* * The reply to client was previously blocked, we will * send again. */ if (send(cli->cli_sd, cli->cli_saved_reply, cli->cli_saved_size, 0) != cli->cli_saved_size) { if (errno != EWOULDBLOCK) { logdebug("handle_req: send: %s", strerror(errno)); rc = ILB_STATUS_SEND; goto err_out; } goto blocked; } free(cli->cli_saved_reply); cli->cli_saved_reply = NULL; cli->cli_saved_size = 0; } done: /* Re-associate with the event port for more requests. */ cli->cli_ev = ILBD_EVENT_REQ; if (port_associate(ev_port, PORT_SOURCE_FD, cli->cli_sd, POLLRDNORM, cli) == -1) { logperror("handle_req: port_associate(POLLRDNORM)"); rc = ILB_STATUS_INTERNAL; goto err_out; } return; blocked: /* Re-associate with the event port. */ cli->cli_ev = ILBD_EVENT_REP_OK; if (port_associate(ev_port, PORT_SOURCE_FD, cli->cli_sd, POLLWRNORM, cli) == -1) { logperror("handle_req: port_associate(POLLWRNORM)"); rc = ILB_STATUS_INTERNAL; goto err_out; } return; err_out: ilbd_free_cli(cli); } static void i_ilbd_read_config(int ev_port) { logdebug("i_ilbd_read_config: port %d", ev_port); (void) ilbd_walk_sg_pgs(ilbd_create_sg, &ev_port, NULL); (void) ilbd_walk_hc_pgs(ilbd_create_hc, &ev_port, NULL); (void) ilbd_walk_rule_pgs(ilbd_create_rule, &ev_port, NULL); } /* * main event loop for ilbd * asserts that argument 'listener' is a server socket ready to accept() on. */ static void main_loop(int listener) { port_event_t p_ev; int ev_port, ev_port_obj; ilbd_event_obj_t ev_obj; ilbd_timer_event_obj_t timer_ev_obj; ev_port = port_create(); if (ev_port == -1) { logperror("main_loop: port_create failed"); exit(-1); } ilbd_hc_timer_init(ev_port, &timer_ev_obj); ev_obj.ev = ILBD_EVENT_NEW_REQ; if (port_associate(ev_port, PORT_SOURCE_FD, listener, POLLRDNORM, &ev_obj) == -1) { logperror("main_loop: port_associate failed"); exit(1); } i_ilbd_read_config(ev_port); ilbd_hc_timer_update(&timer_ev_obj); _NOTE(CONSTCOND) while (B_TRUE) { int r; ilbd_event_t event; ilbd_client_t *cli; r = port_get(ev_port, &p_ev, NULL); if (r == -1) { if (errno == EINTR) continue; logperror("main_loop: port_get failed"); break; } ev_port_obj = p_ev.portev_object; event = ((ilbd_event_obj_t *)p_ev.portev_user)->ev; switch (event) { case ILBD_EVENT_TIMER: ilbd_hc_timeout(); break; case ILBD_EVENT_PROBE: ilbd_hc_probe_return(ev_port, ev_port_obj, p_ev.portev_events, (ilbd_hc_probe_event_t *)p_ev.portev_user); break; case ILBD_EVENT_NEW_REQ: assert(ev_port_obj == listener); /* * An error happens in the listener. Exit * for now.... */ if (p_ev.portev_events & (POLLHUP|POLLERR)) { logerr("main_loop: listener error"); exit(1); } new_req(ev_port, ev_port_obj, &ev_obj); break; case ILBD_EVENT_REP_OK: case ILBD_EVENT_REQ: cli = (ilbd_client_t *)p_ev.portev_user; assert(ev_port_obj == cli->cli_sd); /* * 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 */ if (p_ev.portev_events & (POLLHUP|POLLERR)) { ilbd_free_cli(cli); break; } handle_req(ev_port, event, cli); break; default: logerr("main_loop: unknown event %d", event); exit(EXIT_FAILURE); break; } ilbd_hc_timer_update(&timer_ev_obj); } } static void i_ilbd_setup_lists(void) { i_setup_sg_hlist(); i_setup_rule_hlist(); i_ilbd_setup_hc_list(); } /* * Usage message - call only during startup. it will print its * message on stderr and exit */ static void Usage(char *name) { (void) fprintf(stderr, gettext("Usage: %s [-d|--debug]\n"), name); exit(1); } static void print_version(char *name) { (void) printf("%s %s\n", basename(name), ILBD_VERSION); (void) printf(gettext(ILBD_COPYRIGHT)); 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) { struct rlimit rlp; if (getrlimit(RLIMIT_NOFILE, &rlp) == -1) { perror("ilbd: getrlimit"); exit(errno); } rlp.rlim_cur = rlp.rlim_max; if (setrlimit(RLIMIT_NOFILE, &rlp) == -1) { perror("ilbd: setrlimit"); exit(errno); } } int main(int argc, char **argv) { int s; int c; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif static const char daemon_dir[] = DAEMON_DIR; (void) textdomain(TEXT_DOMAIN); while ((c = getopt(argc, argv, ":V?d(debug)")) != -1) { switch ((char)c) { case '?': Usage(argv[0]); /* not reached */ break; case 'V': print_version(argv[0]); /* not reached */ break; case 'd': ilbd_enable_debug(); break; default: Usage(argv[0]); /* 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. */ ilbd_reset_kernel_state(); /* Increase the limit on the number of file descriptors. */ set_rlim(); /* * ilbd daemon starts off as root, just so it can create * /var/run/daemon if one does not exist. After that is done * the daemon switches to "daemon" uid. This is similar to what * rpcbind does. */ if (mkdir(daemon_dir, DAEMON_DIR_MODE) == 0 || errno == EEXIST) { (void) chmod(daemon_dir, DAEMON_DIR_MODE); (void) chown(daemon_dir, DAEMON_UID, DAEMON_GID); } else { perror("main: mkdir failed"); exit(errno); } /* * Now lets switch ilbd as uid = daemon, gid = daemon with a * trimmed down privilege set */ if (__init_daemon_priv(PU_RESETGROUPS | PU_LIMITPRIVS | PU_INHERITPRIVS, DAEMON_UID, DAEMON_GID, PRIV_PROC_OWNER, PRIV_PROC_AUDIT, PRIV_NET_ICMPACCESS, PRIV_SYS_IP_CONFIG, NULL) == -1) { (void) fprintf(stderr, "Insufficient privileges\n"); exit(EXIT_FAILURE); } /* * 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"); exit(EXIT_FAILURE); } } (void) priv_set(PRIV_OFF, PRIV_INHERITABLE, PRIV_PROC_OWNER, PRIV_PROC_AUDIT, NULL); /* if daemonified then set up syslog */ if (!is_debugging_on()) openlog("ilbd", LOG_PID, LOG_DAEMON); i_ilbd_setup_lists(); 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); ilbd_reset_kernel_state(); return (0); } /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 #include #include #include #include #include #include #include #include #include "libilb_impl.h" #include "ilbd.h" /* * We only allow one show nat/persist command running at any time. Note that * there is no lock for this since ilbd is single threaded. And we only care * about the pointer value of client, not its type. * * The following variables store the current client making the request. */ static void *nat_cur_cli; static void *sticky_cur_cli; /* Maximum number of NAT/sticky entries to request from kernel. */ #define NUM_ENTRIES 500 /* * Clear the current requesting client. This will allow a new client * to make a request. */ void ilbd_show_nat_cleanup(void) { nat_cur_cli = NULL; } void ilbd_show_sticky_cleanup(void) { sticky_cur_cli = NULL; } /* * To show the kernel NAT table. * * cli: the client pointer making the request. * ic: the client request. * rbuf: reply buffer to be filled in. * rbufsz: reply buffer size. */ ilb_status_t ilbd_show_nat(void *cli, const ilb_comm_t *ic, uint32_t *rbuf, size_t *rbufsz) { ilb_show_info_t *req_si = (ilb_show_info_t *)&ic->ic_data; ilb_list_nat_cmd_t *kcmd; boolean_t start; size_t tmp_rbufsz, kbufsz; uint32_t max_num; ilb_status_t ret; int i; ilb_show_info_t *reply; ilb_nat_info_t *nat_ret; /* For new client request, start from the beginning of the table. */ if (nat_cur_cli == NULL) { nat_cur_cli = cli; start = B_TRUE; } else if (cli == nat_cur_cli) { /* * Another request from client. If the client does not * want to continue, reset the current client and reply OK. */ if (ic->ic_flags & ILB_COMM_END) { ilbd_show_nat_cleanup(); ilbd_reply_ok(rbuf, rbufsz); return (ILB_STATUS_OK); } start = B_FALSE; } else { /* A request is on-going, so reject a new client. */ return (ILB_STATUS_INPROGRESS); } tmp_rbufsz = *rbufsz; ilbd_reply_ok(rbuf, rbufsz); reply = (ilb_show_info_t *)&((ilb_comm_t *)rbuf)->ic_data; /* * Calculate the max number of ilb_nat_info_t can be fitted in the * reply. */ *rbufsz += sizeof (ilb_show_info_t *); tmp_rbufsz -= *rbufsz; max_num = tmp_rbufsz / sizeof (ilb_nat_info_t); /* * Calculate the exact number of entries we should request from kernel. */ max_num = min(req_si->sn_num, min(NUM_ENTRIES, max_num)); kbufsz = max_num * sizeof (ilb_nat_entry_t) + offsetof(ilb_list_nat_cmd_t, entries); if ((kcmd = malloc(kbufsz)) == NULL) { logdebug("ilbd_show_nat: malloc(cmd)"); ilbd_reply_err(rbuf, rbufsz, ILB_STATUS_ENOMEM); return (ILB_STATUS_ENOMEM); } kcmd->cmd = ILB_LIST_NAT_TABLE; kcmd->flags = start ? ILB_LIST_BEGIN : ILB_LIST_CONT; kcmd->num_nat = max_num; if ((ret = do_ioctl(kcmd, kbufsz)) != ILB_STATUS_OK) { logperror("ilbd_show_nat: ioctl(ILB_LIST_NAT_TABLE)"); ilbd_reply_err(rbuf, rbufsz, ret); free(kcmd); return (ret); } reply->sn_num = kcmd->num_nat; *rbufsz += reply->sn_num * sizeof (ilb_nat_info_t); /* * It is the end of table, let the client know. And the transaction * is done. */ if (kcmd->flags & ILB_LIST_END) { nat_cur_cli = NULL; } else { /* * ilbd_reply_ok() sets ic_flags to ILB_COMM_END by default. * Need to clear it here. */ ((ilb_comm_t *)rbuf)->ic_flags = 0; } nat_ret = (ilb_nat_info_t *)&reply->sn_data; for (i = 0; i < kcmd->num_nat; i++) { ilb_nat_entry_t *nat; nat = &kcmd->entries[i]; nat_ret->nat_proto = nat->proto; nat_ret->nat_in_local = nat->in_local; nat_ret->nat_in_global = nat->in_global; nat_ret->nat_out_local = nat->out_local; nat_ret->nat_out_global = nat->out_global; nat_ret->nat_in_local_port = nat->in_local_port; nat_ret->nat_in_global_port = nat->in_global_port; nat_ret->nat_out_local_port = nat->out_local_port; nat_ret->nat_out_global_port = nat->out_global_port; nat_ret++; } free(kcmd); return (ret); } /* * To show the kernel sticky table. * * cli: the client pointer making the request. * req_si: information about the show-persist request. * rbuf: reply buffer to be filled in. * rbufsz: reply buffer size. */ ilb_status_t ilbd_show_sticky(void *cli, const ilb_comm_t *ic, uint32_t *rbuf, size_t *rbufsz) { ilb_show_info_t *req_si = (ilb_show_info_t *)&ic->ic_data; ilb_list_sticky_cmd_t *kcmd; boolean_t start; size_t tmp_rbufsz, kbufsz; uint32_t max_num; ilb_status_t ret; int i; ilb_show_info_t *reply; ilb_persist_info_t *st_ret; /* For new client request, start from the beginning of the table. */ if (sticky_cur_cli == NULL) { sticky_cur_cli = cli; start = B_TRUE; } else if (cli == sticky_cur_cli) { /* * Another request from client. If the client does not * want to continue, reset the current client and reply OK. */ if (ic->ic_flags & ILB_COMM_END) { ilbd_show_sticky_cleanup(); ilbd_reply_ok(rbuf, rbufsz); return (ILB_STATUS_OK); } start = B_FALSE; } else { /* A request is on-going, so reject a new client. */ return (ILB_STATUS_INPROGRESS); } tmp_rbufsz = *rbufsz; ilbd_reply_ok(rbuf, rbufsz); reply = (ilb_show_info_t *)&((ilb_comm_t *)rbuf)->ic_data; /* * Calculate the max number of ilb_persist_info_t can be fitted in the * reply. */ *rbufsz += sizeof (ilb_show_info_t *); tmp_rbufsz -= *rbufsz; max_num = tmp_rbufsz / sizeof (ilb_persist_info_t); /* * Calculate the exact number of entries we should request from kernel. */ max_num = min(req_si->sn_num, min(NUM_ENTRIES, max_num)); kbufsz = max_num * sizeof (ilb_sticky_entry_t) + offsetof(ilb_list_sticky_cmd_t, entries); if ((kcmd = malloc(kbufsz)) == NULL) { logdebug("ilbd_show_nat: malloc(cmd)"); ilbd_reply_err(rbuf, rbufsz, ILB_STATUS_ENOMEM); return (ILB_STATUS_ENOMEM); } kcmd->cmd = ILB_LIST_STICKY_TABLE; kcmd->flags = start ? ILB_LIST_BEGIN : ILB_LIST_CONT; kcmd->num_sticky = max_num; if ((ret = do_ioctl(kcmd, kbufsz)) != ILB_STATUS_OK) { logperror("ilbd_show_nat: ioctl(ILB_LIST_STICKY_TABLE)"); ilbd_reply_err(rbuf, rbufsz, ret); free(kcmd); return (ret); } reply->sn_num = kcmd->num_sticky; *rbufsz += reply->sn_num * sizeof (ilb_persist_info_t); if (kcmd->flags & ILB_LIST_END) { sticky_cur_cli = NULL; } else { /* * ilbd_reply_ok() sets ic_flags to ILB_COMM_END by default. * Need to clear it here. */ ((ilb_comm_t *)rbuf)->ic_flags = 0; } st_ret = (ilb_persist_info_t *)&reply->sn_data; for (i = 0; i < kcmd->num_sticky; i++) { ilb_sticky_entry_t *st; st = &kcmd->entries[i]; (void) strlcpy(st_ret->persist_rule_name, st->rule_name, ILB_NAMESZ); st_ret->persist_req_addr = st->req_addr; st_ret->persist_srv_addr = st->srv_addr; st_ret++; } free(kcmd); return (ret); } /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libilb_impl.h" #include "ilbd.h" /* until we all use AF_* macros ... */ #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 /* protocol type */ #define ADDR_LEN (2 * INET6_ADDRSTRLEN + 1) /* prxy src range */ #define PORT_LEN 6 /* hcport:1-65535 or "ANY" */ 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); } /* * Walk the list of rules and check if its safe to add the * the server to the rule (this is a list of rules hanging * off of a server group) */ 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)) { /* server has single port */ if (rl->irl_topo == ILB_TOPO_DSR) { /* * either we have a DSR rule with a port * range, or both server and rule * have single ports but their values * don't match - this is incompatible */ 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) { /* server has a port range */ if ((rl->irl_topo == ILB_TOPO_DSR) && (r_maxport > r_minport)) { if ((r_minport != srv_minport) || (r_maxport != srv_maxport)) { /* * we have a DSR rule with a port range * and its min and max port values * does not meet that of server's * - this is incompatible */ rc = ILB_STATUS_BADPORT; break; } } else if ((rl->irl_topo == ILB_TOPO_DSR) && (r_maxport == r_minport)) { /* * we have a DSR rule with a single * port and a server with a port range * - this is incompatible */ 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) { /* * we have a NAT/Half-NAT rule with * a port range and server with a port * range and there is a mismatch in the * sizes of the port ranges - this is * incompatible */ 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); } } /* * allocate a new daemon-specific rule from the "template" passed * in in *r */ 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; /* find position of rule in list */ 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); } /* * get exactly one rule (named in rl->irl_name) data from kernel */ 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)); /* * Check if the various timeout values are 0. If one is, get the * default values from kernel. */ 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; /* * as far as talking to the kernel is concerned, "all rules" * is handled in one go somewhere else, so we only * tell the kernel about single rules here. */ 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); /* * When dissociating a rule, only two errors can happen. The hc * name is incorrect or the rule is not associated with the hc * object. Both should not happen.... The check is for debugging * purpose. */ 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); } /* * the following two functions are the other's opposite, and can * call into each other for roll back purposes in case of error. * To avoid endless recursion, the 'is_rollback' parameter must be * set to B_TRUE in the roll back case. */ 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; /* no use sending a no-op to the kernel */ if ((irl->irl_flags & ILB_FLAGS_RULE_ENABLED) != 0) return (ILB_STATUS_OK); irl->irl_flags |= ILB_FLAGS_RULE_ENABLED; /* "all rules" is handled in one go somewhere else, not here */ 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) { /* Undo the kernel work */ kcmd.cmd = ILB_DISABLE_RULE; /* Cannot do much if ioctl fails... */ (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) /* ignore rollback return code */ (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; /* no use sending a no-op to the kernel */ if ((irl->irl_flags & ILB_FLAGS_RULE_ENABLED) == 0) return (ILB_STATUS_OK); irl->irl_flags &= ~ILB_FLAGS_RULE_ENABLED; /* "all rules" is handled in one go somewhere else, not here */ 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) { /* Undo the kernel work */ kcmd.cmd = ILB_ENABLE_RULE; /* Cannot do much if ioctl fails... */ (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) /* ignore rollback return code */ (void) ilbd_enable_one_rule(irl, B_TRUE); } return (rc); } /* * Generates an audit record for a supplied rule name * Used for enable_rule, disable_rule, delete_rule, * and create_rule subcommands */ 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; /* algo:topo */ char *valstr1 = NULL; char *valstr2 = NULL; char pbuf[PROTOCOL_LEN]; /* protocol */ char hcpbuf[PORT_LEN]; /* hcport */ int audit_error; if ((ucredp == NULL) && (cmd == ILBD_CREATE_RULE)) { /* * we came here from the path where ilbd incorporates * the configuration that is listed in SCF : * i_ilbd_read_config->ilbd_walk_rule_pgs-> * ->ilbd_scf_instance_walk_pg->ilbd_create_rule * We skip auditing in that case */ 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; /* Fill in virtual IP address type */ 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); } /* Fill in port - could be a single value or a range */ event->adt_ilb_create_rule.min_port = ntohs(rlinfo->rl_minport); if (ntohs(rlinfo->rl_maxport) > ntohs(rlinfo->rl_minport)) { /* port range */ event->adt_ilb_create_rule.max_port = ntohs(rlinfo->rl_maxport); } else { /* in audit record, max=min when single port */ event->adt_ilb_create_rule.max_port = ntohs(rlinfo->rl_minport); } /* * Fill in protocol - if user does not specify it, * its TCP by default */ 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; /* Fill in algorithm and operation type */ 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; /* Fill in proxy-src for the NAT case */ if (rlinfo->rl_topo == ILB_TOPO_NAT) { /* copy starting proxy-src address */ if (IN6_IS_ADDR_V4MAPPED(&rlinfo->rl_nat_src_start)) { /* V4 case */ 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 { /* V6 case */ 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); } /* copy ending proxy-src address */ if (IN6_IS_ADDR_UNSPECIFIED(&rlinfo->rl_nat_src_end)) { /* proxy-src is a single address */ 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)) { /* * proxy-src is a address range - copy ending * proxy-src address * V4 case */ 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 { /* V6 case */ 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); } } /* * Fill in pmask if user has specified one - 0 means * no persistence */ valstr1[0] = '\0'; ilbd_ip_to_str(rlinfo->rl_ipversion, &rlinfo->rl_stickymask, valstr1); event->adt_ilb_create_rule.persist_mask = valstr1; /* If there is a hcname */ if (rlinfo->rl_hcname[0] != '\0') event->adt_ilb_create_rule.hcname = rlinfo->rl_hcname; /* Fill in hcport */ if (rlinfo->rl_hcpflag == ILB_HCI_PROBE_FIX) { /* hcport is specified by user */ (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) { /* user has specified "ANY" */ (void) snprintf(hcpbuf, PORT_LEN, "ANY"); event->adt_ilb_create_rule.hcport = hcpbuf; } /* * Fill out the conndrain, nat_timeout and persist_timeout * If the user does not specify them, the default value * is set in the kernel. Userland does not know what * the values are. So if the user * does not specify these values they will show up as * 0 in the audit record. */ 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; /* Fill out servergroup and rule name */ 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); } /* * converts IP address from in6_addr format to uint32_t[4] * This conversion is needed for recording IP address in * audit records. */ void cvt_addr(uint32_t *audit, int32_t type, struct in6_addr address) { if (type == ADT_IPv4) { /* address is IPv4 */ audit[0] = address._S6_un._S6_u32[3]; } else { /* address is IPv6 */ (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); /* generate the audit record before bailing out */ 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; /* just one rule */ 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; } /* auditing will be done by i_ilbd_action_switch() */ rc = i_ilbd_action_switch(irl, cmd, B_FALSE, ucredp); goto out; } /* all rules: first tell the kernel, then walk the daemon's list */ 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; /* auditing will be done by i_ilbd_action_switch() */ 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); } /* current list element failed, so we start with previous one */ irl = list_prev(&ilbd_rule_hlist, irl); while (irl != NULL) { if (is_all_rules) irl->irl_flags |= ILB_FLAGS_RULE_ALLRULES; /* * When the processing of a command consists of * multiple sequential steps, and one of them fails, * ilbd performs rollback to undo the steps taken before the * failing step. Since ilbd is initiating these steps * there is not need to audit them. */ 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)); } /* * allocate storage for a kernel rule command and fill from * "template" irl, if non-NULL */ 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); } /* * ncount is the next to be used index into (*kcmdp)->servers */ 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); /* * the first ilb_server_info_t is part of *kcmd, so * by using index (which is one less than the total needed) here, * we allocate exactly the amount we need. */ 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); /* * we don't count the slot we newly allocated yet. */ kcmd->num_servers = index; *kcmdp = kcmd; return (ILB_STATUS_OK); } /* * this function adds all servers in srvlist to the kernel(!) rule * the name of which is passed as argument. */ 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 the servergroup doesn't have any servers associated with * it yet, there's nothing more to do here. */ if (sg->isg_srvcount == 0) return (ILB_STATUS_OK); /* * walk the list of servers attached to this SG */ 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]); /* * "no port" means "copy rule's port" (for kernel rule) */ 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); /* * if adding even a single server failed, we need to * roll back the whole wad. We ignore any errors and * return the one that was returned by the first ioctl. */ kcmd->cmd = ILB_DEL_SERVERS; (void) do_ioctl(kcmd, 0); goto rollback_kcmd; } } rollback_kcmd: free(kcmd); return (rc); } /* convert a struct in6_addr to valstr */ 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); } /* allocs and copies contents of arg (if != NULL) into new rule */ 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); } /* make sure rule's IPversion (via vip) and SG's match */ 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; /* Try associating the rule with the given hc oject. */ if (RULE_HAS_HC(irl)) { if ((rc = ilbd_hc_associate_rule(irl, ev_port)) != ILB_STATUS_OK) goto out; } /* * checks are done, now: * 1. create rule in kernel * 2. tell it about the backend server (which we maintain in SG) * 3. attach the rule in memory */ /* 1. */ /* allocs and copies contents of arg (if != NULL) into new rule */ 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; /* 2. */ rc = i_update_ksrv_rules(kcmd->name, sg, irl); if (rc != ILB_STATUS_OK) goto rollback_kcmd; /* 3. */ (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: /* * ilbd_destroy_one_rule() also frees irl, as well as dissociate * rule and HC, so all we need to do afterwards is free the kcmd * and return. */ (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: /* Cannot fail since the rule is just associated with the hc object. */ 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; /* more as they are defined */ return (r); } /* * walk the list of rules and add srv to the *kernel* rule * (this is a list of rules hanging off of a server group) */ 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)); /* * a note about rollback: since we need to start rollback with the * current list element in some case, and with the previous one * in others, we must "go back" in this latter case before * we jump to the rollback code. */ for (rl = list_head(rlist); rl != NULL; rl = list_next(rlist, rl)) { (void) strlcpy(kcmd.name, rl->irl_name, sizeof (kcmd.name)); /* * sgs_minport == 0 means "no port specified"; this * indicates that the server matches anything the rule * provides. * NOTE: this can be different for different rules * using the same server group, therefore we don't modify * this information in the servergroup, but *only* in * the kernel's rule. */ 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 ioctl() returns != 0, it doesn't perform the copyout * necessary to indicate *which* server failed (we could be * adding more than one); therefore we must check this * 'err' field even if ioctl() returns 0. */ 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: /* * this is almost, but not quite, the same as i_rem_srv_frm_krules() * therefore we keep it seperate. */ 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); } /* * ev_port is only used for rollback purposes in this function */ 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 ioctl() returns != 0, it doesn't perform the copyout * necessary to indicate *which* server failed (we could be * removing more than one); therefore we must check this * 'err' field even if ioctl() returns 0. */ 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: /* Don't do roll back if ev_port == -1. */ 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); } /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include "libilb_impl.h" #include "ilbd.h" #define ILBD_PG_NAME_RULE "rule_" #define ILBD_PG_NAME_SG "sg_" #define ILBD_PG_NAME_HC "hc_" #define ILBD_SVC_FMRI "svc:/network/loadbalancer/ilb" #define ILBD_INST_NAME "default" typedef enum { ILBD_RULE_STATUS, ILBD_RULE_VIP, ILBD_RULE_PROTO, ILBD_RULE_PORT, ILBD_RULE_ALGO, ILBD_RULE_TOPO, ILBD_RULE_NAT_STR, ILBD_RULE_NAT_END, ILBD_RULE_STI_MASK, ILBD_RULE_SGNAME, ILBD_RULE_HCNAME, ILBD_RULE_HCPORT, ILBD_RULE_HCPFLAG, ILBD_RULE_DRAINTIME, ILBD_RULE_NAT_TO, ILBD_RULE_PERS_TO, ILBD_SG_SERVER, ILBD_HC_TEST, ILBD_HC_TIMEOUT, ILBD_HC_INTERVAL, ILBD_HC_DEF_PING, ILBD_HC_COUNT, ILBD_VAR_INVALID } ilbd_var_type_t; typedef struct prop_tbl_entry { ilbd_var_type_t val_type; const char *scf_propname; scf_type_t scf_proptype; } prop_tbl_entry_t; /* * this table contains a map of all SCF properties, including rules, * servergroups and health checks. The place to add new property needs to be * watched carefully. When new properties are added, corresponding *VAR_NUM * needs to be adjusted to reflect the correct index of the table */ prop_tbl_entry_t prop_tbl[] = { /* entried for rule */ {ILBD_RULE_STATUS, "status", SCF_TYPE_BOOLEAN}, /* SCF_TYPE_NET_ADDR_V4 or SCF_TYPE_NET_ADDR_V6 */ {ILBD_RULE_VIP, "vip", SCF_TYPE_INVALID}, {ILBD_RULE_PROTO, "protocol", SCF_TYPE_ASTRING}, {ILBD_RULE_PORT, "port", SCF_TYPE_ASTRING}, {ILBD_RULE_ALGO, "ilb-algo", SCF_TYPE_ASTRING}, {ILBD_RULE_TOPO, "ilb-type", SCF_TYPE_ASTRING}, {ILBD_RULE_NAT_STR, "ilb-nat-start", SCF_TYPE_INVALID}, {ILBD_RULE_NAT_END, "ilb-nat-end", SCF_TYPE_INVALID}, {ILBD_RULE_STI_MASK, "ilb-sti-mask", SCF_TYPE_INVALID}, {ILBD_RULE_SGNAME, "servergroup", SCF_TYPE_ASTRING}, {ILBD_RULE_HCNAME, "healthcheck", SCF_TYPE_ASTRING}, {ILBD_RULE_HCPORT, "hc-port", SCF_TYPE_INTEGER}, {ILBD_RULE_HCPFLAG, "hcp-flag", SCF_TYPE_INTEGER}, {ILBD_RULE_DRAINTIME, "drain-time", SCF_TYPE_INTEGER}, {ILBD_RULE_NAT_TO, "nat-timeout", SCF_TYPE_INTEGER}, {ILBD_RULE_PERS_TO, "pers-timeout", SCF_TYPE_INTEGER}, /* add new rule related prop here */ /* entries for sg */ {ILBD_SG_SERVER, "server", SCF_TYPE_ASTRING}, /* add new sg related prop here */ /* entries for hc */ {ILBD_HC_TEST, "test", SCF_TYPE_ASTRING}, {ILBD_HC_TIMEOUT, "timeout", SCF_TYPE_INTEGER}, {ILBD_HC_INTERVAL, "interval", SCF_TYPE_INTEGER}, {ILBD_HC_DEF_PING, "ping", SCF_TYPE_BOOLEAN}, /* add new hc related prop here */ {ILBD_HC_COUNT, "count", SCF_TYPE_INTEGER} }; #define ILBD_PROP_VAR_NUM (ILBD_HC_COUNT + 1) #define ILBD_RULE_VAR_NUM (ILBD_SG_SERVER) #define ILBD_SG_VAR_NUM (ILBD_HC_TEST - ILBD_SG_SERVER) #define ILBD_HC_VAR_NUM (ILBD_PROP_VAR_NUM - ILBD_HC_TEST) static ilb_status_t ilbd_scf_set_prop(scf_propertygroup_t *, const char *, scf_type_t, scf_value_t *); static ilb_status_t ilbd_scf_retrieve_pg(const char *, scf_propertygroup_t **, boolean_t); static ilb_status_t ilbd_scf_delete_pg(scf_propertygroup_t *); static ilb_status_t ilbd_scf_get_prop_val(scf_propertygroup_t *, const char *, scf_value_t **); #define MIN(a, b) ((a) < (b) ? (a) : (b)) int ilbd_scf_limit(int type) { return (MIN(scf_limit(type), 120)); } /* * Translate libscf error to libilb status */ ilb_status_t ilbd_scf_err_to_ilb_err() { switch (scf_error()) { case SCF_ERROR_NONE: return (ILB_STATUS_OK); case SCF_ERROR_HANDLE_MISMATCH: case SCF_ERROR_HANDLE_DESTROYED: case SCF_ERROR_VERSION_MISMATCH: case SCF_ERROR_NOT_BOUND: case SCF_ERROR_CONSTRAINT_VIOLATED: case SCF_ERROR_NOT_SET: case SCF_ERROR_TYPE_MISMATCH: case SCF_ERROR_INVALID_ARGUMENT: return (ILB_STATUS_EINVAL); case SCF_ERROR_NO_MEMORY: case SCF_ERROR_NO_RESOURCES: return (ILB_STATUS_ENOMEM); case SCF_ERROR_NOT_FOUND: case SCF_ERROR_DELETED: return (ILB_STATUS_ENOENT); case SCF_ERROR_EXISTS: return (ILB_STATUS_EEXIST); case SCF_ERROR_PERMISSION_DENIED: return (ILB_STATUS_PERMIT); case SCF_ERROR_CALLBACK_FAILED: return (ILB_STATUS_CALLBACK); case SCF_ERROR_IN_USE: return (ILB_STATUS_INUSE); default: return (ILB_STATUS_INTERNAL); } } static void ilbd_name_to_scfpgname(ilbd_scf_pg_type_t pg_type, const char *pgname, char *scf_pgname) { switch (pg_type) { case ILBD_SCF_RULE: (void) snprintf(scf_pgname, ILBD_MAX_NAME_LEN, ILBD_PG_NAME_RULE "%s", pgname); return; case ILBD_SCF_SG: (void) snprintf(scf_pgname, ILBD_MAX_NAME_LEN, ILBD_PG_NAME_SG "%s", pgname); return; case ILBD_SCF_HC: (void) snprintf(scf_pgname, ILBD_MAX_NAME_LEN, ILBD_PG_NAME_HC "%s", pgname); return; /* Should not happen. Log it and put ILB service in maintenance. */ default: logerr("ilbd_name_to_scfpgname: invalid pg type %d for pg %s", pg_type, pgname); (void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE); exit(EXIT_FAILURE); return; } } static void ilbd_scf_destroy(scf_handle_t *h, scf_service_t *s, scf_instance_t *inst, scf_propertygroup_t *pg) { if (pg != NULL) scf_pg_destroy(pg); if (inst != NULL) scf_instance_destroy(inst); if (s != NULL) scf_service_destroy(s); if (h != NULL) scf_handle_destroy(h); } static ilb_status_t ilbd_scf_get_inst(scf_handle_t **h, scf_service_t **svc, scf_instance_t **inst) { if ((*h = scf_handle_create(SCF_VERSION)) == NULL) return (ILB_STATUS_INTERNAL); if (scf_handle_bind(*h) != 0) { ilbd_scf_destroy(*h, NULL, NULL, NULL); return (ilbd_scf_err_to_ilb_err()); } if ((*svc = scf_service_create(*h)) == NULL) { ilbd_scf_destroy(*h, NULL, NULL, NULL); return (ilbd_scf_err_to_ilb_err()); } if (scf_handle_decode_fmri(*h, ILBD_SVC_FMRI, NULL, *svc, NULL, NULL, NULL, SCF_DECODE_FMRI_EXACT) != 0) { ilbd_scf_destroy(*h, *svc, NULL, NULL); return (ilbd_scf_err_to_ilb_err()); } if ((*inst = scf_instance_create(*h)) == NULL) { ilbd_scf_destroy(*h, *svc, NULL, NULL); return (ilbd_scf_err_to_ilb_err()); } if (scf_service_get_instance(*svc, ILBD_INST_NAME, *inst) != 0) { ilbd_scf_destroy(*h, *svc, *inst, NULL); return (ilbd_scf_err_to_ilb_err()); } return (ILB_STATUS_OK); } /* * If create is set, create a new prop group, destroy the old one if exists. * If create not set, try to find the prop group with given name. * The created or found entry is returned as *pg. * Caller frees *pg and its handle scf_pg_handle(pg) */ static ilb_status_t ilbd_scf_retrieve_pg(const char *pgname, scf_propertygroup_t **pg, boolean_t create) { scf_instance_t *inst; scf_handle_t *h; scf_service_t *svc; ilb_status_t ret; ret = ilbd_scf_get_inst(&h, &svc, &inst); if (ret != ILB_STATUS_OK) return (ret); *pg = scf_pg_create(h); if (*pg == NULL) return (ILB_STATUS_INTERNAL); if (scf_instance_get_pg(inst, pgname, *pg) != 0) { if (scf_error() != SCF_ERROR_NOT_FOUND || (scf_error() == SCF_ERROR_NOT_FOUND && (!create))) { ilbd_scf_destroy(h, svc, inst, *pg); *pg = NULL; return (ilbd_scf_err_to_ilb_err()); } } else { /* * Found pg, don't want to create, return EEXIST. Note that * h cannot be destroyed here since the caller needs to use it. * The caller gets it by calling scf_pg_handle(). */ if (!create) { ilbd_scf_destroy(NULL, svc, inst, NULL); return (ILB_STATUS_EEXIST); } /* found pg, need to create, destroy the existing one */ else (void) ilbd_scf_delete_pg(*pg); } if (create) { if (scf_instance_add_pg(inst, pgname, SCF_GROUP_APPLICATION, 0, *pg) != 0) { ilbd_scf_destroy(h, svc, inst, *pg); *pg = NULL; return (ilbd_scf_err_to_ilb_err()); } } /* * Note that handle cannot be destroyed here, caller sometimes needs * to use it. It gets the handle by calling scf_pg_handle(). */ ilbd_scf_destroy(NULL, svc, inst, NULL); return (ILB_STATUS_OK); } struct algo_tbl_entry { ilb_algo_t algo_type; const char *algo_str; } algo_tbl[] = { {ILB_ALG_ROUNDROBIN, "ROUNDROBIN"}, {ILB_ALG_HASH_IP, "HASH-IP"}, {ILB_ALG_HASH_IP_SPORT, "HASH-IP-PORT"}, {ILB_ALG_HASH_IP_VIP, "HASH-IP-VIP"} }; #define ILBD_ALGO_TBL_SIZE (sizeof (algo_tbl) / \ sizeof (*algo_tbl)) void ilbd_algo_to_str(ilb_algo_t algo_type, char *valstr) { int i; for (i = 0; i < ILBD_ALGO_TBL_SIZE; i++) { if (algo_type == algo_tbl[i].algo_type) { (void) strlcpy(valstr, algo_tbl[i].algo_str, ILBD_MAX_VALUE_LEN); return; } } logerr("ilbd_algo_to_str: algo not found"); } static void ilbd_scf_str_to_algo(ilb_algo_t *algo_type, char *valstr) { int i; for (i = 0; i < ILBD_ALGO_TBL_SIZE; i++) { if (strcmp(valstr, algo_tbl[i].algo_str) == 0) { *algo_type = algo_tbl[i].algo_type; return; } } logerr("ilbd_scf_str_to_algo: algo not found"); } struct topo_tbl_entry { ilb_topo_t topo_type; const char *topo_str; } topo_tbl[] = { {ILB_TOPO_DSR, "DSR"}, {ILB_TOPO_NAT, "NAT"}, {ILB_TOPO_HALF_NAT, "HALF-NAT"} }; #define ILBD_TOPO_TBL_SIZE (sizeof (topo_tbl) / \ sizeof (*topo_tbl)) void ilbd_topo_to_str(ilb_topo_t topo_type, char *valstr) { int i; for (i = 0; i < ILBD_TOPO_TBL_SIZE; i++) { if (topo_type == topo_tbl[i].topo_type) { (void) strlcpy(valstr, topo_tbl[i].topo_str, ILBD_MAX_VALUE_LEN); return; } } logerr("ilbd_scf_topo_to_str: topo not found"); } static void ilbd_scf_str_to_topo(ilb_topo_t *topo_type, char *valstr) { int i; for (i = 0; i < ILBD_TOPO_TBL_SIZE; i++) { if (strcmp(valstr, topo_tbl[i].topo_str) == 0) { *topo_type = topo_tbl[i].topo_type; return; } } logerr("ilbd_scf_str_to_topo: topo not found"); } static void ilbd_get_svr_field(char *valstr, struct in6_addr *sgs_addr, int32_t *min_port, int32_t *max_port, int32_t *sgs_flags) { char *ipaddr, *ipverstr, *portstr, *flagstr; int ip_ver; ilb_ip_addr_t temp_ip; void *addrptr; char *max_portstr; ipaddr = strtok(valstr, ";"); ipverstr = strtok(NULL, ";"); portstr = strtok(NULL, ";"); flagstr = strtok(NULL, ";"); if (ipaddr == NULL || ipverstr == NULL || portstr == NULL || flagstr == NULL) { logerr("%s: invalid server fields", __func__); (void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE); exit(EXIT_FAILURE); } ip_ver = atoi(ipverstr); addrptr = (ip_ver == AF_INET) ? (void *)&temp_ip.ia_v4 : (void *)&temp_ip.ia_v6; if (inet_pton(ip_ver, ipaddr, addrptr) == 0) { logerr("ilbd_get_svr_field: inet_pton failed"); return; } if (ip_ver == AF_INET) { IN6_INADDR_TO_V4MAPPED(&(temp_ip.ia_v4), sgs_addr); } else { (void) memcpy(sgs_addr, &(temp_ip.ia_v6), sizeof (struct in6_addr)); } *sgs_flags = atoi(flagstr); *min_port = atoi(strtok(portstr, "-")); *min_port = ntohs(*min_port); max_portstr = strtok(NULL, "-"); if (max_portstr != NULL) { *max_port = atoi(max_portstr); *max_port = ntohs(*max_port); } } /* * Convert the info of a server to its SCF string value representation. * Argument value is assumed to be of size ILBD_MAX_VALUE_LEN. */ static void ilbd_srv_scf_val(ilbd_srv_t *srv, char *value) { char ipstr[INET6_ADDRSTRLEN]; int ipver; if (GET_AF(&srv->isv_addr) == AF_INET) { struct in_addr v4_addr; IN6_V4MAPPED_TO_INADDR(&srv->isv_addr, &v4_addr); (void) inet_ntop(AF_INET, &v4_addr, ipstr, sizeof (ipstr)); ipver = AF_INET; } else { (void) inet_ntop(AF_INET6, &srv->isv_addr, ipstr, sizeof (ipstr)); ipver = AF_INET6; } (void) snprintf(value, ILBD_MAX_VALUE_LEN, "%s;%d;%d-%d;%d", ipstr, ipver, ntohs(srv->isv_minport), ntohs(srv->isv_maxport), srv->isv_flags); } /* get the "ip:port:status" str of the #num server in the servergroup */ ilb_status_t ilbd_get_svr_info(ilbd_sg_t *sg, int num, char *valstr, char *svrname) { int i; ilbd_srv_t *tmp_srv = NULL; tmp_srv = list_head(&sg->isg_srvlist); if (tmp_srv == NULL) return (ILB_STATUS_ENOENT); for (i = 0; i < num; i++) tmp_srv = list_next(&sg->isg_srvlist, tmp_srv); assert(tmp_srv != NULL); if (valstr != NULL) ilbd_srv_scf_val(tmp_srv, valstr); if (svrname != NULL) { (void) snprintf(svrname, ILBD_MAX_NAME_LEN, "server%d", tmp_srv->isv_id); } return (ILB_STATUS_OK); } /* convert a struct in6_addr to valstr */ ilb_status_t ilbd_scf_ip_to_str(uint16_t ipversion, struct in6_addr *addr, scf_type_t *scftype, char *valstr) { size_t vallen; ilb_ip_addr_t ipaddr; void *addrptr; vallen = (ipversion == AF_INET) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN; if (scftype != NULL) *scftype = (ipversion == AF_INET) ? SCF_TYPE_NET_ADDR_V4 : SCF_TYPE_NET_ADDR_V6; IP_COPY_IMPL_2_CLI(addr, &ipaddr); addrptr = (ipversion == AF_INET) ? (void *)&ipaddr.ia_v4 : (void *)&ipaddr.ia_v6; (void) inet_ntop(ipversion, (void *)addrptr, valstr, vallen); return (ILB_STATUS_OK); } /* * This function takes a ilbd internal data struct and translate its value to * scf value. The data struct is passed in within "data". * Upon successful return, the scf val will be stored in "val" and the scf type * will be returned in "scftype" if scftype != NULL, the number of values * translated will be in "numval" * If it failed, no data will be written to SCF */ static ilb_status_t ilbd_data_to_scfval(ilbd_scf_pg_type_t pg_type, ilbd_var_type_t type, scf_handle_t *h, void *data, scf_value_t ***val, scf_type_t *scftype, int *numval) { scf_value_t *v, **varray = NULL; int ret = ILB_STATUS_OK; int i; int scf_val_len = ILBD_MAX_VALUE_LEN; char *valstr = NULL; int valint; uint8_t valbool = 0; ilbd_rule_t *r_ent = NULL; ilbd_sg_t *s_ent = NULL; ilbd_hc_t *h_ent = NULL; switch (pg_type) { case ILBD_SCF_RULE: r_ent = (ilbd_rule_t *)data; break; case ILBD_SCF_SG: s_ent = (ilbd_sg_t *)data; break; case ILBD_SCF_HC: h_ent = (ilbd_hc_t *)data; break; } v = scf_value_create(h); if (v == NULL) return (ILB_STATUS_INTERNAL); if ((valstr = malloc(scf_val_len)) == NULL) return (ILB_STATUS_ENOMEM); switch (type) { case ILBD_RULE_STATUS: valbool = r_ent->irl_flags & ILB_FLAGS_RULE_ENABLED; break; case ILBD_RULE_VIP: ret = ilbd_scf_ip_to_str(r_ent->irl_ipversion, &r_ent->irl_vip, scftype, valstr); if (ret != ILB_STATUS_OK) { free(valstr); scf_value_destroy(v); return (ret); } break; case ILBD_RULE_PROTO: { struct protoent *protoent; protoent = getprotobynumber(r_ent->irl_proto); (void) strlcpy(valstr, protoent->p_name, scf_val_len); break; } case ILBD_RULE_PORT: (void) snprintf(valstr, scf_val_len, "%d-%d", r_ent->irl_minport, r_ent->irl_maxport); break; case ILBD_RULE_ALGO: ilbd_algo_to_str(r_ent->irl_algo, valstr); break; case ILBD_RULE_TOPO: ilbd_topo_to_str(r_ent->irl_topo, valstr); break; case ILBD_RULE_NAT_STR: ret = ilbd_scf_ip_to_str(r_ent->irl_ipversion, &r_ent->irl_nat_src_start, scftype, valstr); if (ret != ILB_STATUS_OK) { free(valstr); scf_value_destroy(v); return (ret); } break; case ILBD_RULE_NAT_END: ret = ilbd_scf_ip_to_str(r_ent->irl_ipversion, &r_ent->irl_nat_src_end, scftype, valstr); if (ret != ILB_STATUS_OK) { free(valstr); scf_value_destroy(v); return (ret); } break; case ILBD_RULE_STI_MASK: ret = ilbd_scf_ip_to_str(r_ent->irl_ipversion, &r_ent->irl_stickymask, scftype, valstr); if (ret != ILB_STATUS_OK) { free(valstr); scf_value_destroy(v); return (ret); } break; case ILBD_RULE_SGNAME: (void) strlcpy(valstr, r_ent->irl_sgname, scf_val_len); break; case ILBD_RULE_HCNAME: if (r_ent->irl_hcname[0] != '\0') (void) strlcpy(valstr, r_ent->irl_hcname, scf_val_len); else bzero(valstr, ILBD_MAX_VALUE_LEN); break; case ILBD_RULE_HCPORT: valint = r_ent->irl_hcport; break; case ILBD_RULE_HCPFLAG: valint = r_ent->irl_hcpflag; break; case ILBD_RULE_DRAINTIME: valint = r_ent->irl_conndrain; break; case ILBD_RULE_NAT_TO: valint = r_ent->irl_nat_timeout; break; case ILBD_RULE_PERS_TO: valint = r_ent->irl_sticky_timeout; break; case ILBD_SG_SERVER: if (s_ent->isg_srvcount == 0) { (void) strlcpy(valstr, "EMPTY_SERVERGROUP", scf_val_len); break; } varray = calloc(sizeof (*varray), s_ent->isg_srvcount); if (varray == NULL) { scf_value_destroy(v); free(valstr); return (ILB_STATUS_ENOMEM); } for (i = 0; i < s_ent->isg_srvcount; i++) { if (v == NULL) { for (i--; i >= 0; i--) scf_value_destroy(varray[i]); free(valstr); return (ILB_STATUS_ENOMEM); } ret = ilbd_get_svr_info(s_ent, i, valstr, NULL); if (ret != ILB_STATUS_OK) { scf_value_destroy(v); for (i--; i >= 0; i--) scf_value_destroy(varray[i]); free(valstr); free(varray); return (ret); } (void) scf_value_set_astring(v, valstr); varray[i] = v; v = scf_value_create(h); } /* the last 'v' we created will go unused, so drop it */ scf_value_destroy(v); *numval = s_ent->isg_srvcount; *val = varray; free(valstr); return (ret); case ILBD_HC_TEST: (void) strlcpy(valstr, h_ent->ihc_test, scf_val_len); break; case ILBD_HC_TIMEOUT: valint = h_ent->ihc_timeout; break; case ILBD_HC_INTERVAL: valint = h_ent->ihc_interval; break; case ILBD_HC_DEF_PING: valbool = h_ent->ihc_def_ping; break; case ILBD_HC_COUNT: valint = h_ent->ihc_count; break; } switch (*scftype) { case SCF_TYPE_BOOLEAN: scf_value_set_boolean(v, valbool); break; case SCF_TYPE_ASTRING: (void) scf_value_set_astring(v, valstr); break; case SCF_TYPE_INTEGER: scf_value_set_integer(v, valint); break; case SCF_TYPE_NET_ADDR_V4: (void) scf_value_set_from_string(v, SCF_TYPE_NET_ADDR_V4, valstr); break; case SCF_TYPE_NET_ADDR_V6: (void) scf_value_set_from_string(v, SCF_TYPE_NET_ADDR_V6, valstr); break; } free(valstr); varray = calloc(1, sizeof (*varray)); if (varray == NULL) { scf_value_destroy(v); return (ILB_STATUS_ENOMEM); } varray[0] = v; *val = varray; *numval = 1; return (ret); } /* * create a scf property group */ ilb_status_t ilbd_create_pg(ilbd_scf_pg_type_t pg_type, void *data) { ilb_status_t ret; char *pgname; scf_propertygroup_t *pg = NULL; scf_value_t **val; scf_handle_t *h; int scf_name_len = ILBD_MAX_NAME_LEN; char *scfpgbuf; /* property group name or group type */ int i, i_st, i_end; switch (pg_type) { case ILBD_SCF_RULE: { ilbd_rule_t *r_ent = (ilbd_rule_t *)data; pgname = r_ent->irl_name; i_st = 0; i_end = ILBD_RULE_VAR_NUM; break; } case ILBD_SCF_SG: { ilbd_sg_t *s_ent = (ilbd_sg_t *)data; pgname = s_ent->isg_name; i_st = ILBD_RULE_VAR_NUM; i_end = ILBD_RULE_VAR_NUM + ILBD_SG_VAR_NUM; break; } case ILBD_SCF_HC: { ilbd_hc_t *h_ent = (ilbd_hc_t *)data; pgname = h_ent->ihc_name; i_st = ILBD_RULE_VAR_NUM + ILBD_SG_VAR_NUM; i_end = ILBD_PROP_VAR_NUM; break; } default: logdebug("ilbd_create_pg: invalid pg type %d for pg %s", pg_type, pgname); return (ILB_STATUS_EINVAL); } if ((scfpgbuf = malloc(scf_name_len)) == NULL) return (ILB_STATUS_ENOMEM); ilbd_name_to_scfpgname(pg_type, pgname, scfpgbuf); ret = ilbd_scf_retrieve_pg(scfpgbuf, &pg, B_TRUE); if (ret != ILB_STATUS_OK) { free(scfpgbuf); return (ret); } h = scf_pg_handle(pg); /* fill in props */ for (i = i_st; i < i_end; i++) { int num, j; scf_type_t scftype = prop_tbl[i].scf_proptype; ret = ilbd_data_to_scfval(pg_type, prop_tbl[i].val_type, h, data, &val, &scftype, &num); if (ret != ILB_STATUS_OK) goto done; for (j = 0; j < num; j++) { if (pg_type == ILBD_SCF_SG) { ret = ilbd_get_svr_info(data, j, NULL, scfpgbuf); if (ret == ILB_STATUS_ENOENT) { (void) strlcpy(scfpgbuf, "EMPTY_SERVER", scf_name_len); } ret = ilbd_scf_set_prop(pg, scfpgbuf, scftype, val[j]); } else { ret = ilbd_scf_set_prop(pg, prop_tbl[i].scf_propname, scftype, val[j]); } scf_value_destroy(val[j]); } free(val); } done: free(scfpgbuf); ilbd_scf_destroy(h, NULL, NULL, pg); return (ret); } /* * destroy a scf property group */ static ilb_status_t ilbd_scf_delete_pg(scf_propertygroup_t *pg) { if (scf_pg_delete(pg) != 0) return (ilbd_scf_err_to_ilb_err()); return (ILB_STATUS_OK); } /* sg can have same name as rule */ ilb_status_t ilbd_destroy_pg(ilbd_scf_pg_type_t pg_t, const char *pgname) { ilb_status_t ret; scf_propertygroup_t *pg; int scf_name_len = ILBD_MAX_NAME_LEN; char *scfname; if ((scfname = malloc(scf_name_len)) == NULL) return (ILB_STATUS_ENOMEM); ilbd_name_to_scfpgname(pg_t, pgname, scfname); ret = ilbd_scf_retrieve_pg(scfname, &pg, B_FALSE); free(scfname); if (ret != ILB_STATUS_EEXIST) return (ret); ret = ilbd_scf_delete_pg(pg); ilbd_scf_destroy(scf_pg_handle(pg), NULL, NULL, pg); return (ret); } /* * Set named property to scf value specified. If property is new, * create it. */ static ilb_status_t ilbd_scf_set_prop(scf_propertygroup_t *pg, const char *propname, scf_type_t proptype, scf_value_t *val) { scf_handle_t *h = NULL; scf_property_t *prop = NULL; scf_value_t *oldval = NULL; scf_transaction_t *tx = NULL; scf_transaction_entry_t *ent = NULL; boolean_t new = B_FALSE; ilb_status_t ret = ILB_STATUS_OK; int commit_ret; h = scf_pg_handle(pg); if (h == NULL || propname == NULL) return (ILB_STATUS_EINVAL); ret = ilbd_scf_get_prop_val(pg, propname, &oldval); if (oldval != NULL) scf_value_destroy(oldval); if (ret == ILB_STATUS_ENOENT) new = B_TRUE; else if (ret != ILB_STATUS_OK) return (ret); if ((prop = scf_property_create(h)) == NULL) return (ilbd_scf_err_to_ilb_err()); if ((tx = scf_transaction_create(h)) == NULL || (ent = scf_entry_create(h)) == NULL) { ret = ilbd_scf_err_to_ilb_err(); logdebug("ilbd_scf_set_prop: create scf transaction failed\n"); goto out; } if (scf_transaction_start(tx, pg) == -1) { ret = ilbd_scf_err_to_ilb_err(); logdebug("ilbd_scf_set_prop: start scf transaction failed\n"); goto out; } if (new) { if (scf_transaction_property_new(tx, ent, propname, proptype) == -1) { ret = ilbd_scf_err_to_ilb_err(); logdebug("ilbd_scf_set_prop: create scf prop failed\n"); goto out; } } else { if (scf_transaction_property_change(tx, ent, propname, proptype) == -1) { ret = ilbd_scf_err_to_ilb_err(); logdebug("ilbd_scf_set_prop: change scf prop failed\n"); goto out; } } if (scf_entry_add_value(ent, val) != 0) { logdebug("ilbd_scf_set_prop: add scf entry failed\n"); ret = ilbd_scf_err_to_ilb_err(); goto out; } commit_ret = scf_transaction_commit(tx); switch (commit_ret) { case 1: ret = ILB_STATUS_OK; /* update pg here, so subsequent property setting succeeds */ (void) scf_pg_update(pg); break; case 0: /* transaction failed due to not having most recent pg */ ret = ILB_STATUS_INUSE; break; default: ret = ilbd_scf_err_to_ilb_err(); break; } out: if (tx != NULL) scf_transaction_destroy(tx); if (ent != NULL) scf_entry_destroy(ent); if (prop != NULL) scf_property_destroy(prop); return (ret); } /* * get a prop's scf val */ static ilb_status_t ilbd_scf_get_prop_val(scf_propertygroup_t *pg, const char *propname, scf_value_t **val) { scf_handle_t *h = NULL; scf_property_t *prop = NULL; scf_value_t *value = NULL; ilb_status_t ret = ILB_STATUS_OK; h = scf_pg_handle(pg); if (h == NULL || propname == NULL) return (ILB_STATUS_EINVAL); if ((prop = scf_property_create(h)) == NULL) return (ilbd_scf_err_to_ilb_err()); if (scf_pg_get_property(pg, propname, prop) != 0) { ret = ilbd_scf_err_to_ilb_err(); goto out; } if ((value = scf_value_create(h)) == NULL) { ret = ilbd_scf_err_to_ilb_err(); goto out; } if (scf_property_get_value(prop, value) != 0) { scf_value_destroy(value); ret = ilbd_scf_err_to_ilb_err(); goto out; } *val = value; out: if (prop != NULL) scf_property_destroy(prop); return (ret); } typedef struct ilbd_data { union { ilb_sg_info_t *sg_info; ilb_hc_info_t *hc_info; ilb_rule_info_t *rule_info; } data; ilbd_scf_pg_type_t pg_type; /* type of data */ #define sg_data data.sg_info #define hc_data data.hc_info #define rule_data data.rule_info } ilbd_data_t; void ilbd_scf_str_to_ip(int ipversion, char *ipstr, struct in6_addr *addr) { ilb_ip_addr_t ipaddr; void *addrptr; addrptr = (ipversion == AF_INET) ? (void *)&ipaddr.ia_v4 : (void *)&ipaddr.ia_v6; (void) inet_pton(ipversion, ipstr, addrptr); if (ipversion == AF_INET) { IN6_INADDR_TO_V4MAPPED(&(ipaddr.ia_v4), addr); } else { (void) memcpy(addr, &(ipaddr.ia_v6), sizeof (struct in6_addr)); } } /* * This function takes a scf value and writes it to the correct field of the * corresponding data struct. */ static ilb_status_t ilbd_scfval_to_data(const char *propname, ilbd_var_type_t ilb_type, scf_value_t *val, ilbd_data_t *ilb_data) { scf_type_t scf_type = scf_value_type(val); ilbd_scf_pg_type_t pg_type = ilb_data->pg_type; int ret = 0; ilb_rule_info_t *r_ent = NULL; ilb_sg_info_t *s_ent = NULL; ilb_hc_info_t *h_ent = NULL; char ipstr[INET6_ADDRSTRLEN]; char *valstr; int64_t valint; uint8_t valbool; int ipversion; switch (pg_type) { case ILBD_SCF_RULE: r_ent = ilb_data->rule_data; break; case ILBD_SCF_HC: h_ent = ilb_data->hc_data; break; case ILBD_SCF_SG: s_ent = ilb_data->sg_data; break; } /* get scf value out */ if ((valstr = malloc(ILBD_MAX_VALUE_LEN)) == NULL) return (ILB_STATUS_ENOMEM); switch (scf_type) { case SCF_TYPE_NET_ADDR_V4: if (scf_value_get_as_string_typed(val, SCF_TYPE_NET_ADDR_V4, ipstr, INET_ADDRSTRLEN) < 0) { free(valstr); return (ILB_STATUS_INTERNAL); } ipversion = AF_INET; break; case SCF_TYPE_NET_ADDR_V6: if (scf_value_get_as_string_typed(val, SCF_TYPE_NET_ADDR_V6, ipstr, INET6_ADDRSTRLEN) < 0) { free(valstr); return (ILB_STATUS_INTERNAL); } ipversion = AF_INET6; break; case SCF_TYPE_BOOLEAN: if (scf_value_get_boolean(val, &valbool) < 0) { free(valstr); return (ILB_STATUS_INTERNAL); } break; case SCF_TYPE_ASTRING: if (scf_value_get_astring(val, valstr, ILBD_MAX_VALUE_LEN) < 0) { free(valstr); return (ILB_STATUS_INTERNAL); } break; case SCF_TYPE_INTEGER: if (scf_value_get_integer(val, &valint) < 0) { free(valstr); return (ILB_STATUS_INTERNAL); } break; default: free(valstr); return (ILB_STATUS_INTERNAL); } ret = ILB_STATUS_OK; switch (ilb_type) { case ILBD_RULE_STATUS: if (valbool) r_ent->rl_flags |= ILB_FLAGS_RULE_ENABLED; break; case ILBD_RULE_VIP: r_ent->rl_ipversion = ipversion; ilbd_scf_str_to_ip(ipversion, ipstr, &r_ent->rl_vip); break; case ILBD_RULE_PROTO: { struct protoent *protoent; protoent = getprotobyname(valstr); r_ent->rl_proto = protoent->p_proto; break; } case ILBD_RULE_PORT: { char *token1, *token2; token1 = strtok(valstr, "-"); token2 = strtok(NULL, "-"); r_ent->rl_minport = atoi(token1); r_ent->rl_maxport = atoi(token2); break; } case ILBD_RULE_ALGO: ilbd_scf_str_to_algo(&(r_ent->rl_algo), valstr); break; case ILBD_RULE_TOPO: ilbd_scf_str_to_topo(&(r_ent->rl_topo), valstr); break; case ILBD_RULE_NAT_STR: ilbd_scf_str_to_ip(ipversion, ipstr, &r_ent->rl_nat_src_start); break; case ILBD_RULE_NAT_END: ilbd_scf_str_to_ip(ipversion, ipstr, &r_ent->rl_nat_src_end); break; case ILBD_RULE_STI_MASK: ilbd_scf_str_to_ip(ipversion, ipstr, &r_ent->rl_stickymask); if (ipversion == AF_INET) { if (!IN6_IS_ADDR_V4MAPPED_ANY(&r_ent->rl_stickymask)) r_ent->rl_flags |= ILB_FLAGS_RULE_STICKY; } else { if (!IN6_IS_ADDR_UNSPECIFIED(&r_ent->rl_stickymask)) r_ent->rl_flags |= ILB_FLAGS_RULE_STICKY; } break; case ILBD_RULE_SGNAME: (void) strlcpy(r_ent->rl_sgname, valstr, sizeof (r_ent->rl_sgname)); break; case ILBD_RULE_HCNAME: (void) strlcpy(r_ent->rl_hcname, valstr, sizeof (r_ent->rl_hcname)); break; case ILBD_RULE_HCPORT: r_ent->rl_hcport = valint; break; case ILBD_RULE_HCPFLAG: r_ent->rl_hcpflag = valint; break; case ILBD_RULE_DRAINTIME: r_ent->rl_conndrain = valint; break; case ILBD_RULE_NAT_TO: r_ent->rl_nat_timeout = valint; break; case ILBD_RULE_PERS_TO: r_ent->rl_sticky_timeout = valint; break; case ILBD_SG_SERVER: { int svr_cnt = s_ent->sg_srvcount; /* found a new server, increase the svr count of this sg */ s_ent->sg_srvcount++; /* * valstr contains information of one server in the servergroup * valstr is in the format of "ip:minport-maxport:enable" */ s_ent = realloc(s_ent, sizeof (ilb_sg_info_t) + s_ent->sg_srvcount * sizeof (ilb_sg_srv_t)); /* sgs_srvID is the sg name, leave it blank */ /* * sgs_id is the digit in propname, propname is in a format of * "server" + the digital serverID. We get the serverID by * reading from the 7th char of propname. */ s_ent->sg_servers[svr_cnt].sgs_id = atoi(&propname[6]); ilbd_get_svr_field(valstr, &s_ent->sg_servers[svr_cnt].sgs_addr, &s_ent->sg_servers[svr_cnt].sgs_minport, &s_ent->sg_servers[svr_cnt].sgs_maxport, &s_ent->sg_servers[svr_cnt].sgs_flags); ilb_data->sg_data = s_ent; break; } case ILBD_HC_TEST: (void) strlcpy(h_ent->hci_test, valstr, sizeof (h_ent->hci_test)); break; case ILBD_HC_TIMEOUT: h_ent->hci_timeout = valint; break; case ILBD_HC_INTERVAL: h_ent->hci_interval = valint; break; case ILBD_HC_DEF_PING: h_ent->hci_def_ping = valbool; break; case ILBD_HC_COUNT: h_ent->hci_count = valint; break; case ILBD_VAR_INVALID: /* * An empty server group is represented by an invalid * SCF property. So when loading a server group, this * case can be hit. But it should happen only for this * single case. So if it happens in another case, move * the service into maintenance mode. */ if (pg_type != ILBD_SCF_SG || scf_type != SCF_TYPE_ASTRING) { logerr("%s: invalid ilb type", __func__); (void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE); } else { logdebug("%s: invalid ilb type", __func__); } break; } free(valstr); return (ret); } static ilbd_var_type_t ilbd_name_to_valtype(const char *prop_name) { int i; for (i = 0; i < ILBD_PROP_VAR_NUM; i++) if (strncmp(prop_name, prop_tbl[i].scf_propname, strlen(prop_tbl[i].scf_propname)) == 0) return (prop_tbl[i].val_type); logdebug("ilbd_name_to_valtype: couldn't find prop %s", prop_name); return (ILBD_VAR_INVALID); } /* callback for pg_walk_prop, arg is ilbd_data_t */ static ilb_status_t ilbd_scf_load_prop(scf_propertygroup_t *pg, const char *prop_name, void *arg) { scf_handle_t *h; scf_value_t *val; ilb_status_t ret; ilbd_data_t *ilb_data = (ilbd_data_t *)arg; ilbd_var_type_t val_type = ilbd_name_to_valtype(prop_name); h = scf_pg_handle(pg); if (h == NULL) return (ILB_STATUS_EINVAL); ret = ilbd_scf_get_prop_val(pg, prop_name, &val); if (ret == ILB_STATUS_ENOENT) return (ILB_STATUS_OK); else if (ret != ILB_STATUS_OK) return (ret); /* * Load value to ilb_data. */ ret = ilbd_scfval_to_data(prop_name, val_type, val, ilb_data); if (val != NULL) scf_value_destroy(val); return (ret); } /* * walk properties in one prop group, arg is ilbd_data * cb is ilbd_scf_load_prop() */ static ilb_status_t ilbd_scf_pg_walk_props(scf_propertygroup_t *pg, ilb_status_t (*cb)(scf_propertygroup_t *, const char *, void *), void *arg) { scf_handle_t *h; scf_iter_t *propiter; scf_property_t *prop; int scf_name_len = ILBD_MAX_NAME_LEN; char *prop_name = NULL; ilb_status_t ret = ILB_STATUS_OK; int scf_ret = -1; h = scf_pg_handle(pg); if (h == NULL) return (ILB_STATUS_EINVAL); prop = scf_property_create(h); propiter = scf_iter_create(h); if (prop == NULL || propiter == NULL) goto out; if (scf_iter_pg_properties(propiter, pg) != 0) goto out; if ((prop_name = malloc(scf_name_len)) == NULL) { ret = ILB_STATUS_ENOMEM; goto out; } while ((scf_ret = scf_iter_next_property(propiter, prop)) == 1) { if (scf_property_get_name(prop, prop_name, scf_name_len) < 0) { ret = ilbd_scf_err_to_ilb_err(); goto out; } ret = cb(pg, prop_name, arg); if (ret != ILB_STATUS_OK) break; } out: if (prop_name != NULL) free(prop_name); if (scf_ret == -1) ret = ilbd_scf_err_to_ilb_err(); if (prop != NULL) scf_property_destroy(prop); if (propiter != NULL) scf_iter_destroy(propiter); return (ret); } /* cbs are libd_create_X */ static ilb_status_t ilbd_scf_instance_walk_pg(scf_instance_t *inst, ilbd_scf_pg_type_t pg_type, ilb_status_t (*cb)(void *, int, struct passwd *, ucred_t *), void *arg1, void *arg2) { int scf_ret; ilb_status_t ret; scf_handle_t *h; scf_iter_t *pgiter; scf_propertygroup_t *newpg; int port = *((int *)arg1); int scf_name_len = ILBD_MAX_NAME_LEN; char *pg_name = NULL; if (inst == NULL) return (ILB_STATUS_EINVAL); h = scf_instance_handle(inst); if (h == NULL) return (ILB_STATUS_EINVAL); if ((newpg = scf_pg_create(h)) == NULL) return (ilbd_scf_err_to_ilb_err()); if ((pgiter = scf_iter_create(h)) == NULL) { scf_pg_destroy(newpg); return (ilbd_scf_err_to_ilb_err()); } if ((scf_ret = scf_iter_instance_pgs(pgiter, inst)) < 0) goto out; if ((pg_name = malloc(scf_name_len)) == NULL) { ret = ILB_STATUS_ENOMEM; goto out; } while ((scf_ret = scf_iter_next_pg(pgiter, newpg)) > 0) { ilbd_data_t data; if (scf_pg_get_name(newpg, pg_name, scf_name_len) < 0) { ret = ilbd_scf_err_to_ilb_err(); goto out; } /* * if pg name indicates it's a ilb configuration, walk its prop */ data.pg_type = pg_type; data.hc_data = NULL; data.sg_data = NULL; data.rule_data = NULL; switch (pg_type) { case ILBD_SCF_RULE: if (strncmp(ILBD_PG_NAME_RULE, pg_name, strlen(ILBD_PG_NAME_RULE)) == 0) { data.rule_data = calloc(1, sizeof (ilb_rule_info_t)); if (data.rule_data == NULL) { ret = ILB_STATUS_ENOMEM; goto out; } ret = ilbd_scf_pg_walk_props(newpg, ilbd_scf_load_prop, &data); if (ret != ILB_STATUS_OK) goto out; assert(data.rule_data != NULL); /* set rule name */ (void) strlcpy(data.rule_data->rl_name, &pg_name[strlen(ILBD_PG_NAME_RULE)], sizeof (data.rule_data->rl_name)); ret = cb(data.rule_data, port, arg2, NULL); free(data.rule_data); if (ret != ILB_STATUS_OK) goto out; } break; case ILBD_SCF_SG: if (strncmp(ILBD_PG_NAME_SG, pg_name, strlen(ILBD_PG_NAME_SG)) == 0) { data.sg_data = calloc(1, sizeof (ilb_sg_info_t)); if (data.sg_data == NULL) { ret = ILB_STATUS_ENOMEM; goto out; } ret = ilbd_scf_pg_walk_props(newpg, ilbd_scf_load_prop, &data); if (ret != ILB_STATUS_OK) { free(data.sg_data); goto out; } assert(data.sg_data != NULL); /* set sg name */ (void) strlcpy(data.sg_data->sg_name, &pg_name[strlen(ILBD_PG_NAME_SG)], sizeof (data.sg_data->sg_name)); ret = cb(data.sg_data, port, arg2, NULL); if (ret != ILB_STATUS_OK) { free(data.sg_data); goto out; } /* * create a servergroup is two-step operation. * 1. create an empty servergroup. * 2. add server(s) to the group. * * since we are here from: * main_loop()->ilbd_read_config()-> * ilbd_walk_sg_pgs() * there is no cli to send. So in this * path auditing will skip the * adt_set_from_ucred() check */ if (data.sg_data->sg_srvcount > 0) { ret = ilbd_add_server_to_group( data.sg_data, port, NULL, NULL); if (ret != ILB_STATUS_OK) { free(data.sg_data); goto out; } free(data.sg_data); } } break; case ILBD_SCF_HC: if (strncmp(ILBD_PG_NAME_HC, pg_name, strlen(ILBD_PG_NAME_HC)) == 0) { data.hc_data = calloc(1, sizeof (ilb_hc_info_t)); if (data.hc_data == NULL) { ret = ILB_STATUS_ENOMEM; goto out; } ret = ilbd_scf_pg_walk_props(newpg, ilbd_scf_load_prop, &data); if (ret != ILB_STATUS_OK) goto out; assert(data.hc_data != NULL); /* set hc name */ (void) strlcpy(data.hc_data->hci_name, &pg_name[strlen(ILBD_PG_NAME_HC)], sizeof (data.hc_data->hci_name)); ret = cb(data.hc_data, port, arg2, NULL); free(data.hc_data); if (ret != ILB_STATUS_OK) goto out; } break; } } out: if (pg_name != NULL) free(pg_name); if (scf_ret < 0) ret = ilbd_scf_err_to_ilb_err(); scf_pg_destroy(newpg); scf_iter_destroy(pgiter); return (ret); } typedef ilb_status_t (*ilbd_scf_walker_fn)(void *, int, struct passwd *, ucred_t *); ilb_status_t ilbd_walk_rule_pgs(ilb_status_t (*func)(ilb_rule_info_t *, int, const struct passwd *, ucred_t *), void *arg1, void *arg2) { scf_instance_t *inst; scf_handle_t *h; scf_service_t *svc; ilb_status_t ret; ret = ilbd_scf_get_inst(&h, &svc, &inst); if (ret != ILB_STATUS_OK) return (ret); /* get rule prop group, transfer it to ilb_lrule_info_t */ ret = ilbd_scf_instance_walk_pg(inst, ILBD_SCF_RULE, (ilbd_scf_walker_fn)func, arg1, arg2); ilbd_scf_destroy(h, svc, inst, NULL); return (ret); } ilb_status_t ilbd_walk_sg_pgs(ilb_status_t (*func)(ilb_sg_info_t *, int, const struct passwd *, ucred_t *), void *arg1, void *arg2) { scf_instance_t *inst; scf_handle_t *h; scf_service_t *svc; ilb_status_t ret; ret = ilbd_scf_get_inst(&h, &svc, &inst); if (ret != ILB_STATUS_OK) return (ret); ret = ilbd_scf_instance_walk_pg(inst, ILBD_SCF_SG, (ilbd_scf_walker_fn)func, arg1, arg2); ilbd_scf_destroy(h, svc, inst, NULL); return (ret); } ilb_status_t ilbd_walk_hc_pgs(ilb_status_t (*func)(const ilb_hc_info_t *, int, const struct passwd *, ucred_t *), void *arg1, void *arg2) { scf_instance_t *inst; scf_handle_t *h; scf_service_t *svc; ilb_status_t ret; ret = ilbd_scf_get_inst(&h, &svc, &inst); if (ret != ILB_STATUS_OK) return (ret); ret = ilbd_scf_instance_walk_pg(inst, ILBD_SCF_HC, (ilbd_scf_walker_fn)func, arg1, arg2); ilbd_scf_destroy(h, svc, inst, NULL); return (ret); } ilb_status_t ilbd_change_prop(ilbd_scf_pg_type_t pg_type, const char *pg_name, const char *prop_name, void *new_val) { int ret; scf_propertygroup_t *scfpg = NULL; char *scf_pgname = NULL; scf_type_t scftype; scf_value_t *scfval; scf_handle_t *h; if ((scf_pgname = malloc(ILBD_MAX_NAME_LEN)) == NULL) return (ILB_STATUS_ENOMEM); ilbd_name_to_scfpgname(pg_type, pg_name, scf_pgname); ret = ilbd_scf_retrieve_pg(scf_pgname, &scfpg, B_FALSE); free(scf_pgname); if (ret != ILB_STATUS_EEXIST) return (ret); assert(scfpg != NULL); h = scf_pg_handle(scfpg); if (h == NULL) { ret = ILB_STATUS_EINVAL; goto done; } if ((scfval = scf_value_create(h)) == NULL) { ret = ILB_STATUS_ENOMEM; goto done; } if (pg_type == ILBD_SCF_RULE) { scftype = SCF_TYPE_BOOLEAN; scf_value_set_boolean(scfval, *(boolean_t *)new_val); } else if (pg_type == ILBD_SCF_SG) { scftype = SCF_TYPE_ASTRING; (void) scf_value_set_astring(scfval, (char *)new_val); } ret = ilbd_scf_set_prop(scfpg, prop_name, scftype, scfval); done: if (scf_pg_handle(scfpg) != NULL) scf_handle_destroy(scf_pg_handle(scfpg)); if (scfpg != NULL) scf_pg_destroy(scfpg); if (scfval != NULL) scf_value_destroy(scfval); return (ret); } /* * Update the persistent configuration with a new server, srv, added to a * server group, sg. */ ilb_status_t ilbd_scf_add_srv(ilbd_sg_t *sg, ilbd_srv_t *srv) { scf_propertygroup_t *pg; scf_handle_t *h; scf_value_t *val; ilb_status_t ret; int scf_name_len = ILBD_MAX_NAME_LEN; char *buf = NULL; if ((buf = malloc(scf_name_len)) == NULL) return (ILB_STATUS_ENOMEM); ilbd_name_to_scfpgname(ILBD_SCF_SG, sg->isg_name, buf); ret = ilbd_scf_retrieve_pg(buf, &pg, B_FALSE); /* * The server group does not exist in persistent storage. This * cannot happen. Should probably transition the service to * maintenance since it should be there. */ if (ret != ILB_STATUS_EEXIST) { logerr("ilbd_scf_add_srv: SCF update failed - entering" " maintenance mode"); (void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE); free(buf); return (ILB_STATUS_INTERNAL); } if ((h = scf_pg_handle(pg)) == NULL) { ilbd_scf_destroy(NULL, NULL, NULL, pg); free(buf); return (ilbd_scf_err_to_ilb_err()); } if ((val = scf_value_create(h)) == NULL) { ilbd_scf_destroy(h, NULL, NULL, pg); free(buf); return (ILB_STATUS_ENOMEM); } ilbd_srv_scf_val(srv, buf); (void) scf_value_set_astring(val, buf); (void) snprintf(buf, scf_name_len, "server%d", srv->isv_id); ret = ilbd_scf_set_prop(pg, buf, SCF_TYPE_ASTRING, val); free(buf); ilbd_scf_destroy(h, NULL, NULL, pg); scf_value_destroy(val); return (ret); } /* * Delete a server, srv, of a server group, sg, from the persistent * configuration. */ ilb_status_t ilbd_scf_del_srv(ilbd_sg_t *sg, ilbd_srv_t *srv) { ilb_status_t ret; scf_propertygroup_t *pg; scf_handle_t *h; int scf_name_len = ILBD_MAX_NAME_LEN; char *buf; scf_transaction_t *tx = NULL; scf_transaction_entry_t *entry = NULL; if ((buf = malloc(scf_name_len)) == NULL) return (ILB_STATUS_ENOMEM); ilbd_name_to_scfpgname(ILBD_SCF_SG, sg->isg_name, buf); ret = ilbd_scf_retrieve_pg(buf, &pg, B_FALSE); /* * The server group does not exist in persistent storage. This * cannot happen. THe caller of this function puts service in * maintenance mode. */ if (ret != ILB_STATUS_EEXIST) { free(buf); return (ILB_STATUS_INTERNAL); } ret = ILB_STATUS_OK; if ((h = scf_pg_handle(pg)) == NULL) { logdebug("ilbd_scf_del_srv: scf_pg_handle: %s\n", scf_strerror(scf_error())); ilbd_scf_destroy(NULL, NULL, NULL, pg); free(buf); return (ilbd_scf_err_to_ilb_err()); } if ((tx = scf_transaction_create(h)) == NULL || (entry = scf_entry_create(h)) == NULL) { logdebug("ilbd_scf_del_srv: create scf transaction failed: " "%s\n", scf_strerror(scf_error())); ret = ilbd_scf_err_to_ilb_err(); goto out; } (void) snprintf(buf, scf_name_len, "server%d", srv->isv_id); if (scf_transaction_start(tx, pg) == -1) { logdebug("ilbd_scf_set_prop: start scf transaction failed: " "%s\n", scf_strerror(scf_error())); ret = ilbd_scf_err_to_ilb_err(); goto out; } if (scf_transaction_property_delete(tx, entry, buf) == -1) { logdebug("ilbd_scf_set_prop: delete property failed: %s\n", scf_strerror(scf_error())); ret = ilbd_scf_err_to_ilb_err(); goto out; } if (scf_transaction_commit(tx) != 1) { logdebug("ilbd_scf_set_prop: commit transaction failed: %s\n", scf_strerror(scf_error())); ret = ilbd_scf_err_to_ilb_err(); } out: free(buf); if (entry != NULL) scf_entry_destroy(entry); if (tx != NULL) scf_transaction_destroy(tx); ilbd_scf_destroy(h, NULL, NULL, pg); return (ret); } /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libilb_impl.h" #include "ilbd.h" typedef enum { not_searched, stop_found, cont_search, fail_search } srch_ind_t; static list_t ilbd_sg_hlist; static ilb_status_t i_delete_srv(ilbd_sg_t *, ilbd_srv_t *, int); static void i_ilbd_free_srvID(ilbd_sg_t *, int32_t); /* Last parameter to pass to i_find_srv(), specifying the matching mode */ #define MODE_ADDR 1 #define MODE_SRVID 2 static ilbd_srv_t *i_find_srv(list_t *, ilb_sg_srv_t *, int); void i_setup_sg_hlist(void) { list_create(&ilbd_sg_hlist, sizeof (ilbd_sg_t), offsetof(ilbd_sg_t, isg_link)); } /* * allocate storage for a daemon-internal server group, init counters */ static ilbd_sg_t * i_ilbd_alloc_sg(char *name) { ilbd_sg_t *d_sg; d_sg = calloc(sizeof (*d_sg), 1); if (d_sg == NULL) goto out; (void) strlcpy(d_sg->isg_name, name, sizeof (d_sg->isg_name)); list_create(&d_sg->isg_srvlist, sizeof (ilbd_srv_t), offsetof(ilbd_srv_t, isv_srv_link)); list_create(&d_sg->isg_rulelist, sizeof (ilbd_rule_t), offsetof(ilbd_rule_t, irl_sglink)); list_insert_tail(&ilbd_sg_hlist, d_sg); out: return (d_sg); } static ilb_status_t i_ilbd_save_sg(ilbd_sg_t *d_sg, ilbd_scf_cmd_t scf_cmd, const char *prop_name, char *valstr) { switch (scf_cmd) { case ILBD_SCF_CREATE: return (ilbd_create_pg(ILBD_SCF_SG, (void *)d_sg)); case ILBD_SCF_DESTROY: return (ilbd_destroy_pg(ILBD_SCF_SG, d_sg->isg_name)); case ILBD_SCF_ENABLE_DISABLE: if (prop_name == NULL) return (ILB_STATUS_EINVAL); return (ilbd_change_prop(ILBD_SCF_SG, d_sg->isg_name, prop_name, valstr)); default: logdebug("i_ilbd_save_sg: invalid scf cmd %d", scf_cmd); return (ILB_STATUS_EINVAL); } } ilb_status_t i_attach_rule2sg(ilbd_sg_t *sg, ilbd_rule_t *irl) { /* assert: the same rule is attached to any sg only once */ list_insert_tail(&sg->isg_rulelist, irl); return (ILB_STATUS_OK); } static void i_ilbd_free_sg(ilbd_sg_t *sg) { ilbd_srv_t *tmp_srv; if (sg == NULL) return; list_remove(&ilbd_sg_hlist, sg); while ((tmp_srv = list_remove_tail(&sg->isg_srvlist)) != NULL) { i_ilbd_free_srvID(sg, tmp_srv->isv_id); free(tmp_srv); sg->isg_srvcount--; } free(sg); } ilbd_sg_t * i_find_sg_byname(const char *name) { ilbd_sg_t *sg; /* find position of sg in list */ for (sg = list_head(&ilbd_sg_hlist); sg != NULL; sg = list_next(&ilbd_sg_hlist, sg)) { if (strncmp(sg->isg_name, name, sizeof (sg->isg_name)) == 0) return (sg); } return (sg); } /* * Generates an audit record for enable-server, disable-server, remove-server * delete-servergroup, create-servergroup and add-server subcommands. */ static void ilbd_audit_server_event(audit_sg_event_data_t *data, 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 audit_error; if ((ucredp == NULL) && ((cmd == ILBD_ADD_SERVER_TO_GROUP) || (cmd == ILBD_CREATE_SERVERGROUP))) { /* * We came here from the path where ilbd is * incorporating the ILB configuration from * SCF. In that case, we skip auditing */ return; } if (adt_start_session(&ah, NULL, 0) != 0) { logerr("ilbd_audit_server_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_server_event: adt_set_from_ucred failed"); exit(EXIT_FAILURE); } if (cmd == ILBD_ENABLE_SERVER) flag = ADT_ilb_enable_server; else if (cmd == ILBD_DISABLE_SERVER) flag = ADT_ilb_disable_server; else if (cmd == ILBD_REM_SERVER_FROM_GROUP) flag = ADT_ilb_remove_server; else if (cmd == ILBD_ADD_SERVER_TO_GROUP) flag = ADT_ilb_add_server; else if (cmd == ILBD_CREATE_SERVERGROUP) flag = ADT_ilb_create_servergroup; else if (cmd == ILBD_DESTROY_SERVERGROUP) flag = ADT_ilb_delete_servergroup; if ((event = adt_alloc_event(ah, flag)) == NULL) { logerr("ilbd_audit_server_event: adt_alloc_event failed"); exit(EXIT_FAILURE); } (void) memset((char *)event, 0, sizeof (adt_event_data_t)); switch (cmd) { case ILBD_ENABLE_SERVER: event->adt_ilb_enable_server.auth_used = NET_ILB_ENABLE_AUTH; event->adt_ilb_enable_server.server_id = data->ed_serverid; event->adt_ilb_enable_server.server_ipaddress_type = data->ed_ipaddr_type; (void) memcpy(event->adt_ilb_enable_server.server_ipaddress, data->ed_server_address, (sizeof (data->ed_server_address))); break; case ILBD_DISABLE_SERVER: event->adt_ilb_disable_server.auth_used = NET_ILB_ENABLE_AUTH; event->adt_ilb_disable_server.server_id = data->ed_serverid; event->adt_ilb_disable_server.server_ipaddress_type = data->ed_ipaddr_type; (void) memcpy(event->adt_ilb_disable_server.server_ipaddress, data->ed_server_address, (sizeof (data->ed_server_address))); break; case ILBD_REM_SERVER_FROM_GROUP: event->adt_ilb_remove_server.auth_used = NET_ILB_CONFIG_AUTH; event->adt_ilb_remove_server.server_id = data->ed_serverid; event->adt_ilb_remove_server.server_group = data->ed_sgroup; event->adt_ilb_remove_server.server_ipaddress_type = data->ed_ipaddr_type; (void) memcpy(event->adt_ilb_remove_server.server_ipaddress, data->ed_server_address, (sizeof (data->ed_server_address))); break; case ILBD_CREATE_SERVERGROUP: event->adt_ilb_create_servergroup.auth_used = NET_ILB_CONFIG_AUTH; event->adt_ilb_create_servergroup.server_group = data->ed_sgroup; break; case ILBD_ADD_SERVER_TO_GROUP: event->adt_ilb_add_server.auth_used = NET_ILB_CONFIG_AUTH; event->adt_ilb_add_server.server_ipaddress_type = data->ed_ipaddr_type; (void) memcpy(event->adt_ilb_add_server.server_ipaddress, data->ed_server_address, (sizeof (data->ed_server_address))); event->adt_ilb_add_server.server_id = data->ed_serverid; event->adt_ilb_add_server.server_group = data->ed_sgroup; event->adt_ilb_add_server.server_minport = ntohs(data->ed_minport); event->adt_ilb_add_server.server_maxport = ntohs(data->ed_maxport); break; case ILBD_DESTROY_SERVERGROUP: event->adt_ilb_delete_servergroup.auth_used = NET_ILB_CONFIG_AUTH; event->adt_ilb_delete_servergroup.server_group = data->ed_sgroup; break; } /* Fill in success/failure */ if (rc == ILB_STATUS_OK) { if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) { logerr("ilbd_audit_server_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_server_event:" " adt_put_event failed"); exit(EXIT_FAILURE); } } adt_free_event(event); (void) adt_end_session(ah); } ilb_status_t ilbd_destroy_sg(const char *sg_name, const struct passwd *ps, ucred_t *ucredp) { ilb_status_t rc; ilbd_sg_t *tmp_sg; audit_sg_event_data_t audit_sg_data; (void) memset(&audit_sg_data, 0, sizeof (audit_sg_event_data_t)); audit_sg_data.ed_sgroup = (char *)sg_name; rc = ilbd_check_client_config_auth(ps); if (rc != ILB_STATUS_OK) { ilbd_audit_server_event(&audit_sg_data, ILBD_DESTROY_SERVERGROUP, rc, ucredp); return (rc); } tmp_sg = i_find_sg_byname(sg_name); if (tmp_sg == NULL) { logdebug("ilbd_destroy_sg: cannot find specified server" " group %s", sg_name); ilbd_audit_server_event(&audit_sg_data, ILBD_DESTROY_SERVERGROUP, ILB_STATUS_SGUNAVAIL, ucredp); return (ILB_STATUS_SGUNAVAIL); } /* * we only destroy SGs that don't have any rules associated with * them anymore. */ if (list_head(&tmp_sg->isg_rulelist) != NULL) { logdebug("ilbd_destroy_sg: server group %s has rules" " associated with it and thus cannot be" " removed", tmp_sg->isg_name); ilbd_audit_server_event(&audit_sg_data, ILBD_DESTROY_SERVERGROUP, ILB_STATUS_SGINUSE, ucredp); return (ILB_STATUS_SGINUSE); } if (ps != NULL) { rc = i_ilbd_save_sg(tmp_sg, ILBD_SCF_DESTROY, NULL, NULL); if (rc != ILB_STATUS_OK) { ilbd_audit_server_event(&audit_sg_data, ILBD_DESTROY_SERVERGROUP, rc, ucredp); return (rc); } } i_ilbd_free_sg(tmp_sg); ilbd_audit_server_event(&audit_sg_data, ILBD_DESTROY_SERVERGROUP, rc, ucredp); return (rc); } /* ARGSUSED */ /* * Parameter ev_port is not used but has to have for read persistent configure * ilbd_create_sg(), ilbd_create_hc() and ilbd_create_rule() are callbacks * for ilbd_scf_instance_walk_pg() which requires the same signature. */ ilb_status_t ilbd_create_sg(ilb_sg_info_t *sg, int ev_port, const struct passwd *ps, ucred_t *ucredp) { ilb_status_t rc = ILB_STATUS_OK; ilbd_sg_t *d_sg; audit_sg_event_data_t audit_sg_data; (void) memset(&audit_sg_data, 0, sizeof (audit_sg_event_data_t)); audit_sg_data.ed_sgroup = sg->sg_name; if (ps != NULL) { rc = ilbd_check_client_config_auth(ps); if (rc != ILB_STATUS_OK) { ilbd_audit_server_event(&audit_sg_data, ILBD_CREATE_SERVERGROUP, rc, ucredp); return (rc); } } if (i_find_sg_byname(sg->sg_name) != NULL) { logdebug("ilbd_create_sg: server group %s already exists", sg->sg_name); ilbd_audit_server_event(&audit_sg_data, ILBD_CREATE_SERVERGROUP, ILB_STATUS_SGEXISTS, ucredp); return (ILB_STATUS_SGEXISTS); } d_sg = i_ilbd_alloc_sg(sg->sg_name); if (d_sg == NULL) { ilbd_audit_server_event(&audit_sg_data, ILBD_CREATE_SERVERGROUP, ILB_STATUS_ENOMEM, ucredp); return (ILB_STATUS_ENOMEM); } /* * we've successfully created the sg in memory. Before we can * return "success", we need to reflect this in persistent * storage */ if (ps != NULL) { rc = i_ilbd_save_sg(d_sg, ILBD_SCF_CREATE, NULL, NULL); if (rc != ILB_STATUS_OK) { i_ilbd_free_sg(d_sg); ilbd_audit_server_event(&audit_sg_data, ILBD_CREATE_SERVERGROUP, rc, ucredp); return (rc); } } ilbd_audit_server_event(&audit_sg_data, ILBD_CREATE_SERVERGROUP, rc, ucredp); return (rc); } /* * This function checks whether tsrv should/can be inserted before lsrv * and does so if possible. * We keep the list in sorted order so we don't have to search it * in its entirety for overlap every time we insert a new server. * Return code: * stop_found: don't continue searching because we found a place * cont_search: continue with next element in the list * fail_search: search failed (caller translates to ILB_STATUS_EEXIST) */ static srch_ind_t i_test_and_insert(ilbd_srv_t *tsrv, ilbd_srv_t *lsrv, list_t *srvlist) { struct in6_addr *t1, *l1; int fnd; t1 = &tsrv->isv_addr; l1 = &lsrv->isv_addr; if ((fnd = ilb_cmp_in6_addr(t1, l1, NULL)) == 1) return (cont_search); /* search can continue */ if (fnd == 0) { logdebug("i_test_and_insert: specified server already exists"); return (fail_search); } /* the list is kept in ascending order */ list_insert_before(srvlist, lsrv, tsrv); return (stop_found); } /* * copy a server description [ip1,ip2,port1,port2,srvID,flags] */ #define COPY_SERVER(src, dest) \ (dest)->sgs_addr = (src)->sgs_addr; \ (dest)->sgs_minport = (src)->sgs_minport; \ (dest)->sgs_maxport = (src)->sgs_maxport; \ (dest)->sgs_id = (src)->sgs_id; \ (void) strlcpy((dest)->sgs_srvID, (src)->sgs_srvID, \ sizeof ((dest)->sgs_srvID)); \ (dest)->sgs_flags = (src)->sgs_flags static ilb_status_t i_add_srv2sg(ilbd_sg_t *dsg, ilb_sg_srv_t *srv, ilbd_srv_t **ret_srv) { ilb_sg_srv_t *n_sg_srv; list_t *srvlist; srch_ind_t search = not_searched; ilb_status_t rc = ILB_STATUS_OK; ilbd_srv_t *nsrv, *lsrv; in_port_t h_minport, h_maxport; nsrv = calloc(sizeof (*nsrv), 1); if (nsrv == NULL) return (ILB_STATUS_ENOMEM); n_sg_srv = &nsrv->isv_srv; COPY_SERVER(srv, n_sg_srv); /* * port info is in network byte order - we need host byte order * for comparisons purposes */ h_minport = ntohs(n_sg_srv->sgs_minport); h_maxport = ntohs(n_sg_srv->sgs_maxport); if (h_minport != 0 && h_minport > h_maxport) n_sg_srv->sgs_maxport = n_sg_srv->sgs_minport; srvlist = &dsg->isg_srvlist; lsrv = list_head(srvlist); if (lsrv == NULL) { list_insert_head(srvlist, nsrv); } else { while (lsrv != NULL) { search = i_test_and_insert(nsrv, lsrv, srvlist); if (search != cont_search) break; lsrv = list_next(srvlist, lsrv); /* if reaches the end of list, insert to the tail */ if (search == cont_search && lsrv == NULL) list_insert_tail(srvlist, nsrv); } if (search == fail_search) rc = ILB_STATUS_EEXIST; } if (rc == ILB_STATUS_OK) { dsg->isg_srvcount++; *ret_srv = nsrv; } else { free(nsrv); } return (rc); } /* * Allocate a server ID. The algorithm is simple. Just check the ID array * of the server group and find an unused ID. If *set_id is given, it * means that the ID is already allocated and the ID array needs to be * updated. This is the case when ilbd reads from the persistent * configuration. */ static int32_t i_ilbd_alloc_srvID(ilbd_sg_t *sg, int32_t *set_id) { int32_t id; int32_t i; /* The server ID is already allocated, just update the ID array. */ if (set_id != NULL) { assert(sg->isg_id_arr[*set_id] == 0); sg->isg_id_arr[*set_id] = 1; return (*set_id); } /* if we're "full up", give back something invalid */ if (sg->isg_srvcount == MAX_SRVCOUNT) return (BAD_SRVID); i = sg->isg_max_id; for (id = 0; id < MAX_SRVCOUNT; id++) { if (sg->isg_id_arr[(id + i) % MAX_SRVCOUNT] == 0) break; } sg->isg_max_id = (id + i) % MAX_SRVCOUNT; sg->isg_id_arr[sg->isg_max_id] = 1; return (sg->isg_max_id); } /* * Free a server ID by updating the server group's ID array. */ static void i_ilbd_free_srvID(ilbd_sg_t *sg, int32_t id) { assert(sg->isg_id_arr[id] == 1); sg->isg_id_arr[id] = 0; } /* * This function is called by ilbd_add_server_to_group() and * ilb_remove_server_group() to create a audit record for a * failed servicing of add-server/remove-server command */ static void fill_audit_record(ilb_sg_info_t *sg, audit_sg_event_data_t *audit_sg_data, ilbd_cmd_t cmd, ilb_status_t rc, ucred_t *ucredp) { ilb_sg_srv_t *tsrv; int i; for (i = 0; i < sg->sg_srvcount; i++) { tsrv = &sg->sg_servers[i]; if (cmd == ILBD_ADD_SERVER_TO_GROUP) { audit_sg_data->ed_serverid = NULL; if (IN6_IS_ADDR_V4MAPPED(&tsrv->sgs_addr)) { audit_sg_data->ed_ipaddr_type = ADT_IPv4; cvt_addr(audit_sg_data->ed_server_address, ADT_IPv4, tsrv->sgs_addr); } else { audit_sg_data->ed_ipaddr_type = ADT_IPv6; cvt_addr(audit_sg_data->ed_server_address, ADT_IPv6, tsrv->sgs_addr); } audit_sg_data->ed_minport = tsrv->sgs_minport; audit_sg_data->ed_maxport = tsrv->sgs_maxport; audit_sg_data->ed_sgroup = sg->sg_name; } else if (cmd == ILBD_REM_SERVER_FROM_GROUP) { audit_sg_data->ed_serverid = tsrv->sgs_srvID; audit_sg_data->ed_sgroup = sg->sg_name; audit_sg_data->ed_minport = 0; audit_sg_data->ed_maxport = 0; } ilbd_audit_server_event(audit_sg_data, cmd, rc, ucredp); } } /* * the name(s) of the server(s) are encoded in the sg. */ ilb_status_t ilbd_add_server_to_group(ilb_sg_info_t *sg_info, int ev_port, const struct passwd *ps, ucred_t *ucredp) { ilb_status_t rc = ILB_STATUS_OK; ilbd_sg_t *tmp_sg; int i, j; int32_t new_id = BAD_SRVID; int32_t af = AF_UNSPEC; ilbd_srv_t *nsrv; ilb_sg_srv_t *srv; audit_sg_event_data_t audit_sg_data; if (ps != NULL) { rc = ilbd_check_client_config_auth(ps); if (rc != ILB_STATUS_OK) { fill_audit_record(sg_info, &audit_sg_data, ILBD_ADD_SERVER_TO_GROUP, rc, ucredp); return (rc); } } tmp_sg = i_find_sg_byname(sg_info->sg_name); if (tmp_sg == NULL) { logdebug("ilbd_add_server_to_group: server" " group %s does not exist", sg_info->sg_name); fill_audit_record(sg_info, &audit_sg_data, ILBD_ADD_SERVER_TO_GROUP, ILB_STATUS_ENOENT, ucredp); return (ILB_STATUS_ENOENT); } /* * we do the dance with address family below to make sure only * IP addresses in the same AF get into an SG; the first one to get * in sets the "tone" * if this is the first server to join a group, check whether * there's no mismatch with any *rules* already attached */ if (tmp_sg->isg_srvcount > 0) { ilbd_srv_t *tsrv = list_head(&tmp_sg->isg_srvlist); af = GET_AF(&tsrv->isv_addr); } else { ilbd_rule_t *irl = list_head(&tmp_sg->isg_rulelist); if (irl != NULL) af = GET_AF(&irl->irl_vip); } for (i = 0; i < sg_info->sg_srvcount; i++) { srv = &sg_info->sg_servers[i]; (void) memset(&audit_sg_data, 0, sizeof (audit_sg_data)); if (IN6_IS_ADDR_V4MAPPED(&srv->sgs_addr)) { audit_sg_data.ed_ipaddr_type = ADT_IPv4; cvt_addr(audit_sg_data.ed_server_address, ADT_IPv4, srv->sgs_addr); } else { audit_sg_data.ed_ipaddr_type = ADT_IPv6; cvt_addr(audit_sg_data.ed_server_address, ADT_IPv6, srv->sgs_addr); } audit_sg_data.ed_minport = srv->sgs_minport; audit_sg_data.ed_maxport = srv->sgs_maxport; audit_sg_data.ed_sgroup = sg_info->sg_name; /* only test if we have sth to test against */ if (af != AF_UNSPEC) { int32_t sgs_af = GET_AF(&srv->sgs_addr); if (af != sgs_af) { logdebug("address family mismatch with previous" " hosts in servergroup or with rule"); rc = ILB_STATUS_MISMATCHH; ilbd_audit_server_event(&audit_sg_data, ILBD_ADD_SERVER_TO_GROUP, rc, ucredp); goto rollback; } } /* * PS: NULL means daemon is loading configure from scf. * ServerID is already assigned, just update the ID array. */ if (ps != NULL) { new_id = i_ilbd_alloc_srvID(tmp_sg, NULL); if (new_id == BAD_SRVID) { logdebug("ilbd_add_server_to_group: server" "group %s is full, no more servers" " can be added", sg_info->sg_name); rc = ILB_STATUS_SGFULL; ilbd_audit_server_event(&audit_sg_data, ILBD_ADD_SERVER_TO_GROUP, rc, ucredp); goto rollback; } srv->sgs_id = new_id; } else { new_id = i_ilbd_alloc_srvID(tmp_sg, &srv->sgs_id); } /* * here we implement the requirement that server IDs start * with a character that is not legal in hostnames - in our * case, a "_" (underscore). */ (void) snprintf(srv->sgs_srvID, sizeof (srv->sgs_srvID), "%c%s.%d", ILB_SRVID_PREFIX, tmp_sg->isg_name, srv->sgs_id); audit_sg_data.ed_serverid = srv->sgs_srvID; /* * Before we update the kernel rules by adding the server, * we need to make checks and fail if any of the * following is true: * * o if the server has single port and the servergroup * is associated to a DSR rule with a port range * o if the server has a port range and the servergroup * is associated to a DSR rule with a port range and * the rule's min and max port does not exactly * match that of the server's. * o if the the server has a port range and the servergroup * is associated to a NAT/Half-NAT rule with a port range * and the rule's port range size does not match that * of the server's. * o if the rule has a fixed hc port, check that this port * is valid in the server's port specification. */ rc = i_check_srv2rules(&tmp_sg->isg_rulelist, srv); if (rc != ILB_STATUS_OK) { ilbd_audit_server_event(&audit_sg_data, ILBD_ADD_SERVER_TO_GROUP, rc, ucredp); goto rollback; } if ((rc = i_add_srv2sg(tmp_sg, srv, &nsrv)) != ILB_STATUS_OK) { ilbd_audit_server_event(&audit_sg_data, ILBD_ADD_SERVER_TO_GROUP, rc, ucredp); goto rollback; } rc = i_add_srv2krules(&tmp_sg->isg_rulelist, &nsrv->isv_srv, ev_port); if (rc != ILB_STATUS_OK) { ilbd_audit_server_event(&audit_sg_data, ILBD_ADD_SERVER_TO_GROUP, rc, ucredp); /* * The failure may be due to the serverid being on * hold in kernel for connection draining. But ilbd * has no way of knowing that. So we are freeing up * the serverid, and may run into the risk of * having this failure again, if we choose this * serverid when processing the next add-server * command for this servergroup, while connection * draining is underway. We assume that the user * will read the man page after they encounter * this failure, and learn to not add any server * to the servergroup until connection draining of * all servers in the servergroup is complete. * XXX Need to revisit this when connection draining * is reworked */ list_remove(&tmp_sg->isg_srvlist, nsrv); i_ilbd_free_srvID(tmp_sg, nsrv->isv_id); free(nsrv); tmp_sg->isg_srvcount--; goto rollback; } if (ps != NULL) { rc = ilbd_scf_add_srv(tmp_sg, nsrv); if (rc != ILB_STATUS_OK) { /* * The following should not fail since the * server is just added. Just in case, we * pass in -1 as the event port to avoid * roll back in i_rem_srv_frm_krules() called * by i_delete_srv(). */ ilbd_audit_server_event(&audit_sg_data, ILBD_ADD_SERVER_TO_GROUP, rc, ucredp); (void) i_delete_srv(tmp_sg, nsrv, -1); break; } } } if (rc == ILB_STATUS_OK) { ilbd_audit_server_event(&audit_sg_data, ILBD_ADD_SERVER_TO_GROUP, rc, ucredp); return (rc); } rollback: /* * If ilbd is initializing based on the SCF data and something fails, * the only choice is to transition the service to maintanence mode... */ if (ps == NULL) { logerr("%s: failure during initialization -" " entering maintenance mode", __func__); (void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE); return (rc); } /* * we need to roll back all servers previous to the one * that just caused the failure */ for (j = i-1; j >= 0; j--) { srv = &sg_info->sg_servers[j]; /* We should be able to find those servers just added. */ nsrv = i_find_srv(&tmp_sg->isg_srvlist, srv, MODE_SRVID); assert(nsrv != NULL); (void) i_delete_srv(tmp_sg, nsrv, -1); } return (rc); } static srch_ind_t i_match_srvID(ilb_sg_srv_t *sg_srv, ilbd_srv_t *lsrv) { if (strncmp(sg_srv->sgs_srvID, lsrv->isv_srvID, sizeof (sg_srv->sgs_srvID)) == 0) { return (stop_found); } return (cont_search); } /* * Sanity check on a rule's port specification against all the servers' * specification in its associated server group. * * 1. If the health check's probe port (hcport) is specified. * - if server port range is specified, check if hcport is inside * the range * - if no server port is specified (meaning the port range is the same as * the rule's port range), check if hcport is inside the rule's range. * * 2. If a server has no port specification, there is no conflict. * * 3. If the rule's load balance mode is DSR, a server port specification must * be exactly the same as the rule's. * * 4. In other modes (NAT and half-NAT), the server's port range must be * the same as the rule's, unless it is doing port collapsing (the server's * port range is only 1). */ ilb_status_t ilbd_sg_check_rule_port(ilbd_sg_t *sg, ilb_rule_info_t *rl) { ilbd_srv_t *srv; in_port_t r_minport, r_maxport; /* Don't allow adding a rule to a sg with no server, for now... */ if (sg->isg_srvcount == 0) return (ILB_STATUS_SGEMPTY); r_minport = ntohs(rl->rl_minport); r_maxport = ntohs(rl->rl_maxport); for (srv = list_head(&sg->isg_srvlist); srv != NULL; srv = list_next(&sg->isg_srvlist, srv)) { in_port_t srv_minport, srv_maxport; int range; srv_minport = ntohs(srv->isv_minport); srv_maxport = ntohs(srv->isv_maxport); range = srv_maxport - srv_minport; /* * If the rule has a specific probe port, check if that port is * valid in all the servers' port specification. */ if (rl->rl_hcpflag == ILB_HCI_PROBE_FIX) { in_port_t hcport = ntohs(rl->rl_hcport); /* No server port specified. */ if (srv_minport == 0) { if (hcport > r_maxport || hcport < r_minport) { return (ILB_STATUS_BADSG); } } else { if (hcport > srv_maxport || hcport < srv_minport) { return (ILB_STATUS_BADSG); } } } /* * There is no server port specification, so there cannot be * any conflict. */ if (srv_minport == 0) continue; if (rl->rl_topo == ILB_TOPO_DSR) { if (r_minport != srv_minport || r_maxport != srv_maxport) { return (ILB_STATUS_BADSG); } } else { if ((range != r_maxport - r_minport) && range != 0) return (ILB_STATUS_BADSG); } } return (ILB_STATUS_OK); } static srch_ind_t i_match_srvIP(ilb_sg_srv_t *sg_srv, ilbd_srv_t *lsrv) { if (IN6_ARE_ADDR_EQUAL(&sg_srv->sgs_addr, &lsrv->isv_addr)) return (stop_found); return (cont_search); } static ilbd_srv_t * i_find_srv(list_t *srvlist, ilb_sg_srv_t *sg_srv, int cmpmode) { ilbd_srv_t *tmp_srv; srch_ind_t srch_res = cont_search; for (tmp_srv = list_head(srvlist); tmp_srv != NULL; tmp_srv = list_next(srvlist, tmp_srv)) { switch (cmpmode) { case MODE_ADDR: srch_res = i_match_srvIP(sg_srv, tmp_srv); break; case MODE_SRVID: srch_res = i_match_srvID(sg_srv, tmp_srv); break; } if (srch_res == stop_found) break; } if (srch_res == stop_found) return (tmp_srv); return (NULL); } static ilb_status_t i_delete_srv(ilbd_sg_t *sg, ilbd_srv_t *srv, int ev_port) { ilb_status_t rc; rc = i_rem_srv_frm_krules(&sg->isg_rulelist, &srv->isv_srv, ev_port); if (rc != ILB_STATUS_OK) return (rc); list_remove(&sg->isg_srvlist, srv); i_ilbd_free_srvID(sg, srv->isv_id); free(srv); sg->isg_srvcount--; return (ILB_STATUS_OK); } /* * some people argue that returning anything here is * useless - what *do* you do if you can't remove/destroy * something anyway? */ ilb_status_t ilbd_rem_server_from_group(ilb_sg_info_t *sg_info, int ev_port, const struct passwd *ps, ucred_t *ucredp) { ilb_status_t rc = ILB_STATUS_OK; ilbd_sg_t *tmp_sg; ilbd_srv_t *srv, tmp_srv; ilb_sg_srv_t *tsrv; audit_sg_event_data_t audit_sg_data; rc = ilbd_check_client_config_auth(ps); if (rc != ILB_STATUS_OK) { fill_audit_record(sg_info, &audit_sg_data, ILBD_REM_SERVER_FROM_GROUP, rc, ucredp); return (rc); } tmp_sg = i_find_sg_byname(sg_info->sg_name); if (tmp_sg == NULL) { logdebug("%s: server group %s\n does not exist", __func__, sg_info->sg_name); fill_audit_record(sg_info, &audit_sg_data, ILBD_REM_SERVER_FROM_GROUP, ILB_STATUS_SGUNAVAIL, ucredp); return (ILB_STATUS_SGUNAVAIL); } tsrv = &sg_info->sg_servers[0]; audit_sg_data.ed_serverid = tsrv->sgs_srvID; audit_sg_data.ed_sgroup = sg_info->sg_name; assert(sg_info->sg_srvcount == 1); srv = i_find_srv(&tmp_sg->isg_srvlist, &sg_info->sg_servers[0], MODE_SRVID); if (srv == NULL) { logdebug("%s: cannot find server in server group %s", __func__, sg_info->sg_name); ilbd_audit_server_event(&audit_sg_data, ILBD_REM_SERVER_FROM_GROUP, ILB_STATUS_SRVUNAVAIL, ucredp); return (ILB_STATUS_SRVUNAVAIL); } tsrv = &srv->isv_srv; if (IN6_IS_ADDR_V4MAPPED(&tsrv->sgs_addr)) { audit_sg_data.ed_ipaddr_type = ADT_IPv4; cvt_addr(audit_sg_data.ed_server_address, ADT_IPv4, tsrv->sgs_addr); } else { audit_sg_data.ed_ipaddr_type = ADT_IPv6; cvt_addr(audit_sg_data.ed_server_address, ADT_IPv6, tsrv->sgs_addr); } /* * i_delete_srv frees srv, therefore we need to save * this information for ilbd_scf_del_srv */ (void) memcpy(&tmp_srv, srv, sizeof (tmp_srv)); rc = i_delete_srv(tmp_sg, srv, ev_port); if (rc != ILB_STATUS_OK) { ilbd_audit_server_event(&audit_sg_data, ILBD_REM_SERVER_FROM_GROUP, rc, ucredp); return (rc); } if (ps != NULL) { if ((rc = ilbd_scf_del_srv(tmp_sg, &tmp_srv)) != ILB_STATUS_OK) { ilbd_audit_server_event(&audit_sg_data, ILBD_REM_SERVER_FROM_GROUP, rc, ucredp); logerr("%s: SCF update failed - entering maintenance" " mode", __func__); (void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE); } } ilbd_audit_server_event(&audit_sg_data, ILBD_REM_SERVER_FROM_GROUP, rc, ucredp); return (rc); } ilb_status_t ilbd_retrieve_names(ilbd_cmd_t cmd, uint32_t *rbuf, size_t *rbufsz) { ilb_status_t rc = ILB_STATUS_OK; ilbd_namelist_t *nlist; size_t tmp_rbufsz; tmp_rbufsz = *rbufsz; /* Set up the reply buffer. rbufsz will be set to the new size. */ ilbd_reply_ok(rbuf, rbufsz); /* Calculate how much space is left for holding name info. */ *rbufsz += sizeof (ilbd_namelist_t); tmp_rbufsz -= *rbufsz; nlist = (ilbd_namelist_t *)&((ilb_comm_t *)rbuf)->ic_data; nlist->ilbl_count = 0; switch (cmd) { case ILBD_RETRIEVE_SG_NAMES: { ilbd_sg_t *sg; for (sg = list_head(&ilbd_sg_hlist); sg != NULL && tmp_rbufsz >= sizeof (ilbd_name_t); sg = list_next(&ilbd_sg_hlist, sg), tmp_rbufsz -= sizeof (ilbd_name_t)) { (void) strlcpy(nlist->ilbl_name[nlist->ilbl_count++], sg->isg_name, sizeof (ilbd_name_t)); } break; } case ILBD_RETRIEVE_RULE_NAMES: { ilbd_rule_t *irl; extern list_t ilbd_rule_hlist; for (irl = list_head(&ilbd_rule_hlist); irl != NULL && tmp_rbufsz >= sizeof (ilbd_name_t); irl = list_next(&ilbd_rule_hlist, irl), tmp_rbufsz -= sizeof (ilbd_name_t)) { (void) strlcpy(nlist->ilbl_name[nlist->ilbl_count++], irl->irl_name, sizeof (ilbd_name_t)); } break; } case ILBD_RETRIEVE_HC_NAMES: { extern list_t ilbd_hc_list; ilbd_hc_t *hc; for (hc = list_head(&ilbd_hc_list); hc != NULL && tmp_rbufsz >= sizeof (ilbd_name_t); hc = list_next(&ilbd_hc_list, hc)) { (void) strlcpy(nlist->ilbl_name[nlist->ilbl_count++], hc->ihc_name, sizeof (ilbd_name_t)); } break; } default: logdebug("ilbd_retrieve_names: unknown command"); return (ILB_STATUS_INVAL_CMD); } *rbufsz += nlist->ilbl_count * sizeof (ilbd_name_t); return (rc); } ilb_status_t ilbd_retrieve_sg_hosts(const char *sg_name, uint32_t *rbuf, size_t *rbufsz) { ilbd_sg_t *dsg; ilbd_srv_t *dsrv; list_t *srvlist; ilb_sg_info_t *sg_info; size_t tmp_rbufsz; dsg = i_find_sg_byname(sg_name); if (dsg == NULL) { logdebug("ilbd_retrieve_sg_hosts: server group" " %s not found", sg_name); return (ILB_STATUS_SGUNAVAIL); } srvlist = &dsg->isg_srvlist; dsrv = list_head(srvlist); tmp_rbufsz = *rbufsz; ilbd_reply_ok(rbuf, rbufsz); /* Calculate the size to hold all the hosts info. */ *rbufsz += sizeof (ilb_sg_info_t); tmp_rbufsz -= *rbufsz; sg_info = (ilb_sg_info_t *)&((ilb_comm_t *)rbuf)->ic_data; (void) strlcpy(sg_info->sg_name, sg_name, sizeof (sg_info->sg_name)); sg_info->sg_srvcount = 0; while (dsrv != NULL && tmp_rbufsz >= sizeof (ilb_sg_srv_t)) { sg_info->sg_servers[sg_info->sg_srvcount++] = dsrv->isv_srv; dsrv = list_next(srvlist, dsrv); tmp_rbufsz -= sizeof (ilb_sg_srv_t); } *rbufsz += sg_info->sg_srvcount * sizeof (ilb_sg_srv_t); return (ILB_STATUS_OK); } /* * this mapping function works on the assumption that HC only is * active when a server is enabled. */ static ilb_cmd_t i_srvcmd_d2k(ilbd_srv_status_ind_t dcmd) { ilb_cmd_t cmd; switch (dcmd) { case stat_enable_server: case stat_declare_srv_alive: cmd = ILB_ENABLE_SERVERS; break; case stat_disable_server: case stat_declare_srv_dead: cmd = ILB_DISABLE_SERVERS; break; } return (cmd); } ilb_status_t ilbd_k_Xable_server(const struct in6_addr *addr, const char *rlname, ilbd_srv_status_ind_t cmd) { ilb_status_t rc; ilb_servers_cmd_t kcmd; int e; kcmd.cmd = i_srvcmd_d2k(cmd); (void) strlcpy(kcmd.name, rlname, sizeof (kcmd.name)); kcmd.num_servers = 1; kcmd.servers[0].addr = *addr; kcmd.servers[0].err = 0; rc = do_ioctl(&kcmd, 0); if (rc != ILB_STATUS_OK) return (rc); if ((e = kcmd.servers[0].err) != 0) { logdebug("ilbd_k_Xable_server: error %s occurred", strerror(e)); return (ilb_map_errno2ilbstat(e)); } return (rc); } #define IS_SRV_ENABLED(s) ILB_IS_SRV_ENABLED((s)->sgs_flags) #define IS_SRV_DISABLED(s) (!(IS_SRV_ENABLED(s))) #define SET_SRV_ENABLED(s) ILB_SET_ENABLED((s)->sgs_flags) #define SET_SRV_DISABLED(s) ILB_SET_DISABLED((s)->sgs_flags) static ilb_status_t ilbd_Xable_server(ilb_sg_info_t *sg, const struct passwd *ps, ilbd_srv_status_ind_t cmd, ucred_t *ucredp) { ilb_status_t rc = ILB_STATUS_OK; ilbd_sg_t *isg; ilbd_srv_t *tmp_srv; ilb_sg_srv_t *srv; ilbd_rule_t *irl; char *dot; int scf_name_len = ILBD_MAX_NAME_LEN; int scf_val_len = ILBD_MAX_VALUE_LEN; char *prop_name = NULL; ilb_ip_addr_t ipaddr; void *addrptr; char ipstr[INET6_ADDRSTRLEN], *valstr = NULL; int ipver, vallen; char sgname[ILB_NAMESZ]; uint32_t nflags; ilbd_srv_status_ind_t u_cmd; audit_sg_event_data_t audit_sg_data; (void) memset(&audit_sg_data, 0, sizeof (audit_sg_data)); /* we currently only implement a "list" of one */ assert(sg->sg_srvcount == 1); srv = &sg->sg_servers[0]; audit_sg_data.ed_serverid = srv->sgs_srvID; rc = ilbd_check_client_enable_auth(ps); if (rc != ILB_STATUS_OK) { ilbd_audit_server_event(&audit_sg_data, ILBD_ENABLE_SERVER, rc, ucredp); return (rc); } if (srv->sgs_srvID[0] != ILB_SRVID_PREFIX) { switch (cmd) { case stat_disable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_DISABLE_SERVER, ILB_STATUS_EINVAL, ucredp); break; case stat_enable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_ENABLE_SERVER, ILB_STATUS_EINVAL, ucredp); break; } return (ILB_STATUS_EINVAL); } /* * the following asserts that serverIDs are constructed * along the pattern "_""." * so we look for the final "." to recreate the SG name. */ (void) strlcpy(sgname, srv->sgs_srvID + 1, sizeof (sgname)); dot = strrchr(sgname, (int)'.'); if (dot == NULL) { switch (cmd) { case stat_disable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_DISABLE_SERVER, ILB_STATUS_EINVAL, ucredp); break; case stat_enable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_ENABLE_SERVER, ILB_STATUS_EINVAL, ucredp); break; } return (ILB_STATUS_EINVAL); } /* make the non-sg_name part "invisible" */ *dot = '\0'; isg = i_find_sg_byname(sgname); if (isg == NULL) { switch (cmd) { case stat_disable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_DISABLE_SERVER, ILB_STATUS_ENOENT, ucredp); break; case stat_enable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_ENABLE_SERVER, ILB_STATUS_ENOENT, ucredp); break; } return (ILB_STATUS_ENOENT); } tmp_srv = i_find_srv(&isg->isg_srvlist, srv, MODE_SRVID); if (tmp_srv == NULL) { switch (cmd) { case stat_disable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_DISABLE_SERVER, ILB_STATUS_ENOENT, ucredp); break; case stat_enable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_ENABLE_SERVER, ILB_STATUS_ENOENT, ucredp); break; } return (ILB_STATUS_ENOENT); } /* * if server's servergroup is not associated with * a rule, do not enable it. */ irl = list_head(&isg->isg_rulelist); if (irl == NULL) { switch (cmd) { case stat_disable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_DISABLE_SERVER, ILB_STATUS_INVAL_ENBSRVR, ucredp); break; case stat_enable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_ENABLE_SERVER, ILB_STATUS_INVAL_ENBSRVR, ucredp); break; } return (ILB_STATUS_INVAL_ENBSRVR); } /* Fill in the server IP address for audit record */ if (IN6_IS_ADDR_V4MAPPED(&tmp_srv->isv_addr)) { audit_sg_data.ed_ipaddr_type = ADT_IPv4; cvt_addr(audit_sg_data.ed_server_address, ADT_IPv4, tmp_srv->isv_addr); } else { audit_sg_data.ed_ipaddr_type = ADT_IPv6; cvt_addr(audit_sg_data.ed_server_address, ADT_IPv6, tmp_srv->isv_addr); } /* * We have found the server in memory, perform the following * tasks. * * 1. For every rule associated with this SG, * - tell the kernel * - tell the hc * 2. Update our internal state and persistent configuration * if the new state is not the same as the old one. */ /* 1. */ for (; irl != NULL; irl = list_next(&isg->isg_rulelist, irl)) { rc = ilbd_k_Xable_server(&tmp_srv->isv_addr, irl->irl_name, cmd); if (rc != ILB_STATUS_OK) { switch (cmd) { case stat_disable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_DISABLE_SERVER, rc, ucredp); break; case stat_enable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_ENABLE_SERVER, rc, ucredp); break; } goto rollback_rules; } if (!RULE_HAS_HC(irl)) continue; if (cmd == stat_disable_server) { rc = ilbd_hc_disable_server(irl, &tmp_srv->isv_srv); } else { assert(cmd == stat_enable_server); rc = ilbd_hc_enable_server(irl, &tmp_srv->isv_srv); } if (rc != ILB_STATUS_OK) { logdebug("ilbd_Xable_server: cannot toggle srv " "timer, rc =%d, srv =%s%d\n", rc, tmp_srv->isv_srvID, tmp_srv->isv_id); } } /* 2. */ if ((cmd == stat_disable_server && IS_SRV_DISABLED(&tmp_srv->isv_srv)) || (cmd == stat_enable_server && IS_SRV_ENABLED(&tmp_srv->isv_srv))) { switch (cmd) { case stat_disable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_DISABLE_SERVER, ILB_STATUS_OK, ucredp); break; case stat_enable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_ENABLE_SERVER, ILB_STATUS_OK, ucredp); break; } return (ILB_STATUS_OK); } nflags = tmp_srv->isv_flags; if (cmd == stat_enable_server) ILB_SET_ENABLED(nflags); else ILB_SET_DISABLED(nflags); IP_COPY_IMPL_2_CLI(&tmp_srv->isv_addr, &ipaddr); ipver = GET_AF(&tmp_srv->isv_addr); vallen = (ipver == AF_INET) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN; addrptr = (ipver == AF_INET) ? (void *)&ipaddr.ia_v4 : (void *)&ipaddr.ia_v6; if (inet_ntop(ipver, addrptr, ipstr, vallen) == NULL) { logerr("ilbd_Xable_server: failed transfer ip addr to" " str"); if (errno == ENOSPC) rc = ILB_STATUS_ENOMEM; else rc = ILB_STATUS_GENERIC; switch (cmd) { case stat_disable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_DISABLE_SERVER, rc, ucredp); break; case stat_enable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_ENABLE_SERVER, rc, ucredp); break; } goto rollback_rules; } if ((prop_name = malloc(scf_name_len)) == NULL) return (ILB_STATUS_ENOMEM); if ((valstr = malloc(scf_val_len)) == NULL) { free(prop_name); return (ILB_STATUS_ENOMEM); } (void) snprintf(valstr, scf_val_len, "%s;%d;%d-%d;%d", ipstr, ipver, ntohs(tmp_srv->isv_minport), ntohs(tmp_srv->isv_maxport), nflags); (void) snprintf(prop_name, scf_name_len, "server%d", tmp_srv->isv_id); switch (cmd) { case stat_disable_server: rc = i_ilbd_save_sg(isg, ILBD_SCF_ENABLE_DISABLE, prop_name, valstr); if (rc == ILB_STATUS_OK) SET_SRV_DISABLED(&tmp_srv->isv_srv); break; case stat_enable_server: rc = i_ilbd_save_sg(isg, ILBD_SCF_ENABLE_DISABLE, prop_name, valstr); if (rc == ILB_STATUS_OK) SET_SRV_ENABLED(&tmp_srv->isv_srv); break; } free(prop_name); free(valstr); if (rc == ILB_STATUS_OK) { switch (cmd) { case stat_disable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_DISABLE_SERVER, ILB_STATUS_OK, ucredp); break; case stat_enable_server: ilbd_audit_server_event(&audit_sg_data, ILBD_ENABLE_SERVER, ILB_STATUS_OK, ucredp); break; } return (ILB_STATUS_OK); } rollback_rules: if (cmd == stat_disable_server) u_cmd = stat_enable_server; else u_cmd = stat_disable_server; if (irl == NULL) irl = list_tail(&isg->isg_rulelist); else irl = list_prev(&isg->isg_rulelist, irl); for (; irl != NULL; irl = list_prev(&isg->isg_rulelist, irl)) { (void) ilbd_k_Xable_server(&tmp_srv->isv_addr, irl->irl_name, u_cmd); if (!RULE_HAS_HC(irl)) continue; if (u_cmd == stat_disable_server) (void) ilbd_hc_disable_server(irl, &tmp_srv->isv_srv); else (void) ilbd_hc_enable_server(irl, &tmp_srv->isv_srv); } return (rc); } ilb_status_t ilbd_disable_server(ilb_sg_info_t *sg, const struct passwd *ps, ucred_t *ucredp) { return (ilbd_Xable_server(sg, ps, stat_disable_server, ucredp)); } ilb_status_t ilbd_enable_server(ilb_sg_info_t *sg, const struct passwd *ps, ucred_t *ucredp) { return (ilbd_Xable_server(sg, ps, stat_enable_server, ucredp)); } /* * fill in the srvID for the given IP address in the 0th server */ ilb_status_t ilbd_address_to_srvID(ilb_sg_info_t *sg, uint32_t *rbuf, size_t *rbufsz) { ilbd_srv_t *tmp_srv; ilb_sg_srv_t *tsrv; ilbd_sg_t *tmp_sg; ilbd_reply_ok(rbuf, rbufsz); tsrv = (ilb_sg_srv_t *)&((ilb_comm_t *)rbuf)->ic_data; *rbufsz += sizeof (ilb_sg_srv_t); tmp_sg = i_find_sg_byname(sg->sg_name); if (tmp_sg == NULL) return (ILB_STATUS_SGUNAVAIL); tsrv->sgs_addr = sg->sg_servers[0].sgs_addr; tmp_srv = i_find_srv(&tmp_sg->isg_srvlist, tsrv, MODE_ADDR); if (tmp_srv == NULL) return (ILB_STATUS_ENOENT); (void) strlcpy(tsrv->sgs_srvID, tmp_srv->isv_srvID, sizeof (tsrv->sgs_srvID)); return (ILB_STATUS_OK); } /* * fill in the address for the given serverID in the 0th server */ ilb_status_t ilbd_srvID_to_address(ilb_sg_info_t *sg, uint32_t *rbuf, size_t *rbufsz) { ilbd_srv_t *tmp_srv; ilb_sg_srv_t *tsrv; ilbd_sg_t *tmp_sg; ilbd_reply_ok(rbuf, rbufsz); tsrv = (ilb_sg_srv_t *)&((ilb_comm_t *)rbuf)->ic_data; tmp_sg = i_find_sg_byname(sg->sg_name); if (tmp_sg == NULL) return (ILB_STATUS_SGUNAVAIL); (void) strlcpy(tsrv->sgs_srvID, sg->sg_servers[0].sgs_srvID, sizeof (tsrv->sgs_srvID)); tmp_srv = i_find_srv(&tmp_sg->isg_srvlist, tsrv, MODE_SRVID); if (tmp_srv == NULL) return (ILB_STATUS_ENOENT); tsrv->sgs_addr = tmp_srv->isv_addr; *rbufsz += sizeof (ilb_sg_srv_t); return (ILB_STATUS_OK); } /* * Map ilb_status errors to similar errno values from errno.h or * adt_event.h to be used for audit record */ int ilberror2auditerror(ilb_status_t rc) { int audit_error; switch (rc) { case ILB_STATUS_CFGAUTH: audit_error = ADT_FAIL_VALUE_AUTH; break; case ILB_STATUS_ENOMEM: audit_error = ENOMEM; break; case ILB_STATUS_ENOENT: case ILB_STATUS_ENOHCINFO: case ILB_STATUS_INVAL_HCTESTTYPE: case ILB_STATUS_INVAL_CMD: case ILB_STATUS_DUP_RULE: case ILB_STATUS_ENORULE: case ILB_STATUS_SGUNAVAIL: audit_error = ENOENT; break; case ILB_STATUS_EINVAL: case ILB_STATUS_MISMATCHSG: case ILB_STATUS_MISMATCHH: case ILB_STATUS_BADSG: case ILB_STATUS_INVAL_SRVR: case ILB_STATUS_INVAL_ENBSRVR: case ILB_STATUS_BADPORT: audit_error = EINVAL; break; case ILB_STATUS_EEXIST: case ILB_STATUS_SGEXISTS: audit_error = EEXIST; break; case ILB_STATUS_EWOULDBLOCK: audit_error = EWOULDBLOCK; break; case ILB_STATUS_INPROGRESS: audit_error = EINPROGRESS; break; case ILB_STATUS_INTERNAL: case ILB_STATUS_CALLBACK: case ILB_STATUS_PERMIT: case ILB_STATUS_RULE_NO_HC: audit_error = ADT_FAIL_VALUE_PROGRAM; break; case ILB_STATUS_SOCKET: audit_error = ENOTSOCK; break; case ILB_STATUS_READ: case ILB_STATUS_WRITE: audit_error = ENOTCONN; break; case ILB_STATUS_SGINUSE: audit_error = EADDRINUSE; break; case ILB_STATUS_SEND: audit_error = ECOMM; break; case ILB_STATUS_SGFULL: audit_error = EOVERFLOW; break; case ILB_STATUS_NAMETOOLONG: audit_error = ENAMETOOLONG; break; case ILB_STATUS_SRVUNAVAIL: audit_error = EHOSTUNREACH; break; default: audit_error = ADT_FAIL_VALUE_UNKNOWN; break; } return (audit_error); } /* * 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 * or http://www.opensolaris.org/os/licensing. * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libilb_impl.h" #include "ilbd.h" /* * logs error messages, either to stderr or syslog, depending on * the -d option */ static boolean_t ilbd_debugging = B_FALSE; /* Socket to issue ioctl() to the kernel */ static int ksock = -1; void ilbd_enable_debug(void) { ilbd_debugging = B_TRUE; } boolean_t is_debugging_on(void) { return (ilbd_debugging); } /* * All routines log to syslog, unless the daemon is running in * the foreground, in which case the logging goes to stderr. * The following logging functions are available: * * * logdebug(): A printf-like function for outputting debug messages * (messages at LOG_DEBUG) that are only of use to developers. * * logerr(): A printf-like function for outputting error messages * (messages at LOG_ERR) from the daemon. * * logperror*(): A set of functions used to output error messages * (messages at LOG_ERR); these automatically append strerror(errno) * and a newline to the message passed to them. * * NOTE: since the logging functions write to syslog, the messages passed * to them are not eligible for localization. Thus, gettext() must * *not* be used. * */ /* PRINTFLIKE2 */ void ilbd_log(int pri, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (ilbd_debugging == B_TRUE) { (void) vfprintf(stderr, fmt, ap); (void) fprintf(stderr, "\n"); } else { vsyslog(pri, fmt, ap); } va_end(ap); } /* PRINTFLIKE1 */ void logperror(const char *str) { if (ilbd_debugging == B_TRUE) (void) fprintf(stderr, "%s: %s\n", str, strerror(errno)); else syslog(LOG_ERR, "%s: %m", str); } ilb_status_t ilbd_check_client_config_auth(const struct passwd *pwd) { if (chkauthattr(NET_ILB_CONFIG_AUTH, pwd->pw_name) == 0) { logdebug("user %s is not authorized for" " configuration operation", pwd->pw_name); return (ILB_STATUS_CFGAUTH); } return (ILB_STATUS_OK); } ilb_status_t ilbd_check_client_enable_auth(const struct passwd *pwd) { if (chkauthattr(NET_ILB_ENABLE_AUTH, pwd->pw_name) == 0) { logdebug("user %s is not authorized for" " enable/disable operation", pwd->pw_name); return (ILB_STATUS_CFGAUTH); } return (ILB_STATUS_OK); } /* * input param. "err" should be one of the errnos defined in * /usr/include/sys/errno.h * this list is NOT complete. */ ilb_status_t ilb_map_errno2ilbstat(int err) { ilb_status_t rc = ILB_STATUS_INTERNAL; switch (err) { case 0: rc = ILB_STATUS_OK; /* for completeness' sake */ break; case EINVAL: rc = ILB_STATUS_EINVAL; break; case ENOENT: rc = ILB_STATUS_ENOENT; break; case ENOMEM: rc = ILB_STATUS_ENOMEM; break; case EINPROGRESS: rc = ILB_STATUS_INPROGRESS; break; case EEXIST: rc = ILB_STATUS_EEXIST; break; } return (rc); } static int i_get_kcmd_sz(void *cmdp) { int sz; switch (((ilb_rule_cmd_t *)cmdp)->cmd) { case ILB_DESTROY_RULE: case ILB_ENABLE_RULE: case ILB_DISABLE_RULE: sz = sizeof (ilb_name_cmd_t); break; case ILB_CREATE_RULE: case ILB_LIST_RULE: sz = sizeof (ilb_rule_cmd_t); break; case ILB_NUM_RULES: sz = sizeof (ilb_num_rules_cmd_t); break; case ILB_NUM_SERVERS: sz = sizeof (ilb_num_servers_cmd_t); break; case ILB_ADD_SERVERS: { ilb_servers_info_cmd_t *kcmd = (ilb_servers_info_cmd_t *)cmdp; sz = sizeof (*kcmd) + ((kcmd->num_servers - 1) * sizeof (kcmd->servers)); break; } case ILB_RULE_NAMES: { ilb_rule_names_cmd_t *kcmd = (ilb_rule_names_cmd_t *)cmdp; sz = sizeof (*kcmd) + ((kcmd->num_names - 1) * sizeof (kcmd->buf)); break; } case ILB_DEL_SERVERS: case ILB_ENABLE_SERVERS: case ILB_DISABLE_SERVERS: { ilb_servers_cmd_t *kcmd = (ilb_servers_cmd_t *)cmdp; sz = sizeof (*kcmd) + ((kcmd->num_servers - 1) * sizeof (kcmd->servers)); break; } default: sz = -1; break; } return (sz); } /* * parameter 'sz' is optional (indicated by == 0); if it's not set * we try to derive it from cmdp->cmd */ ilb_status_t do_ioctl(void *cmdp, ssize_t sz) { struct strioctl ioc; int i_rc; if (ksock == -1) { ksock = socket(AF_INET, SOCK_DGRAM, 0); if (ksock == -1) { logperror("do_ioctl: AF_INET socket call" " failed"); return (ILB_STATUS_INTERNAL); } } (void) memset(&ioc, 0, sizeof (ioc)); ioc.ic_cmd = SIOCILB; ioc.ic_timout = 0; ioc.ic_dp = cmdp; if (sz == 0) { sz = i_get_kcmd_sz(cmdp); if (sz == -1) { logdebug("do_ioctl: unknown command"); return (ILB_STATUS_INVAL_CMD); } } ioc.ic_len = sz; i_rc = ioctl(ksock, I_STR, (caddr_t)&ioc); if (i_rc == -1) { logdebug("do_ioctl: SIOCILB ioctl (%d) failed: %s", *(ilb_cmd_t *)cmdp, strerror(errno)); return (ilb_map_errno2ilbstat(errno)); } return (ILB_STATUS_OK); } /* * Create an OK reply to a client request. It is assumed that the passed * in buffer is large enough to hold the reply. */ void ilbd_reply_ok(uint32_t *rbuf, size_t *rbufsz) { ilb_comm_t *ic = (ilb_comm_t *)rbuf; ic->ic_cmd = ILBD_CMD_OK; /* Default is one exchange of request/response. */ ic->ic_flags = ILB_COMM_END; *rbufsz = sizeof (ilb_comm_t); } /* * Create an error reply to a client request. It is assumed that the passed * in buffer is large enough to hold the reply. */ void ilbd_reply_err(uint32_t *rbuf, size_t *rbufsz, ilb_status_t status) { ilb_comm_t *ic = (ilb_comm_t *)rbuf; ic->ic_cmd = ILBD_CMD_ERROR; /* Default is one exchange of request/response. */ ic->ic_flags = ILB_COMM_END; *(ilb_status_t *)&ic->ic_data = status; *rbufsz = sizeof (ilb_comm_t) + sizeof (ilb_status_t); }