/* * 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 "ilbadm.h" #define PORT_SEP ':' typedef enum { numeric = 1, non_numeric } addr_type_t; ilbadm_val_type_t algo_types[] = { {(int)ILB_ALG_ROUNDROBIN, "roundrobin", "rr"}, {(int)ILB_ALG_HASH_IP, "hash-ip", "hip"}, {(int)ILB_ALG_HASH_IP_SPORT, "hash-ip-port", "hipp"}, {(int)ILB_ALG_HASH_IP_VIP, "hash-ip-vip", "hipv"}, {ILBD_BAD_VAL, NULL, NULL} }; ilbadm_val_type_t topo_types[] = { {(int)ILB_TOPO_DSR, "DSR", "d"}, {(int)ILB_TOPO_NAT, "NAT", "n"}, {(int)ILB_TOPO_HALF_NAT, "HALF-NAT", "h"}, {ILBD_BAD_VAL, NULL, NULL} }; void ip2str(ilb_ip_addr_t *ip, char *buf, size_t sz, int flags) { int len; switch (ip->ia_af) { case AF_INET: if (*(uint32_t *)&ip->ia_v4 == 0) buf[0] = '\0'; else (void) inet_ntop(AF_INET, (void *)&ip->ia_v4, buf, sz); break; case AF_INET6: if (IN6_IS_ADDR_UNSPECIFIED(&ip->ia_v6)) { buf[0] = '\0'; break; } if (!(flags & V6_ADDRONLY)) *buf++ = '['; sz--; (void) inet_ntop(ip->ia_af, (void *)&ip->ia_v6, buf, sz); if (!(flags & V6_ADDRONLY)) { len = strlen(buf); buf[len] = ']'; buf[++len] = '\0'; } break; default: buf[0] = '\0'; } } char * i_str_from_val(int val, ilbadm_val_type_t *types) { ilbadm_val_type_t *v; for (v = types; v->v_type != ILBD_BAD_VAL; v++) { if (v->v_type == val) break; } /* we return this in all cases */ return (v->v_name); } int i_val_from_str(char *name, ilbadm_val_type_t *types) { ilbadm_val_type_t *v; for (v = types; v->v_type != ILBD_BAD_VAL; v++) { if (strncasecmp(name, v->v_name, sizeof (v->v_name)) == 0 || strncasecmp(name, v->v_alias, sizeof (v->v_alias)) == 0) break; } /* we return this in all cases */ return (v->v_type); } ilbadm_key_code_t i_match_key(char *key, ilbadm_key_name_t *keylist) { ilbadm_key_name_t *t_key; for (t_key = keylist; t_key->k_key != ILB_KEY_BAD; t_key++) { if (strncasecmp(key, t_key->k_name, sizeof (t_key->k_name)) == 0 || strncasecmp(key, t_key->k_alias, sizeof (t_key->k_alias)) == 0) break; } return (t_key->k_key); } /* * try to match: * 1) IPv4 address * 2) IPv6 address * 3) a hostname */ static ilbadm_status_t i_match_onehost(const char *val, ilb_ip_addr_t *ip, addr_type_t *a_type) { struct addrinfo *ai = NULL; struct addrinfo hints; addr_type_t at = numeric; (void) memset((void *)&hints, 0, sizeof (hints)); hints.ai_flags |= AI_NUMERICHOST; /* * if *a_type == numeric, we only want to check whether this * is a (valid) numeric IP address. If we do and it is NOT, * we return _ENOENT. */ if (getaddrinfo(val, NULL, &hints, &ai) != 0) { if (a_type != NULL && (*a_type == numeric)) return (ILBADM_INVAL_ADDR); at = non_numeric; if (getaddrinfo(val, NULL, NULL, &ai) != 0) return (ILBADM_INVAL_ADDR); } ip->ia_af = ai->ai_family; switch (ip->ia_af) { case AF_INET: { struct sockaddr_in sa; assert(ai->ai_addrlen == sizeof (sa)); (void) memcpy(&sa, ai->ai_addr, sizeof (sa)); ip->ia_v4 = sa.sin_addr; break; } case AF_INET6: { struct sockaddr_in6 sa; assert(ai->ai_addrlen == sizeof (sa)); (void) memcpy(&sa, ai->ai_addr, sizeof (sa)); ip->ia_v6 = sa.sin6_addr; break; } default: return (ILBADM_INVAL_AF); } if (a_type != NULL) *a_type = at; return (ILBADM_OK); } static ilbadm_status_t i_store_serverID(void *store, char *val) { ilbadm_servnode_t *s = (ilbadm_servnode_t *)store; ilb_server_data_t *sn = &s->s_spec; /* * we shouldn't need to check for length here, as a name that's * too long won't exist in the system anyway. */ (void) strlcpy(sn->sd_srvID, val, sizeof (sn->sd_srvID)); return (ILBADM_OK); } static struct in_addr i_next_in_addr(struct in_addr *a, int dir) { struct in_addr new_in; uint32_t iah; iah = ntohl(a->s_addr); if (dir == 1) iah++; else iah--; new_in.s_addr = htonl(iah); return (new_in); } static ilbadm_status_t i_expand_ipv4range(ilbadm_sgroup_t *sg, ilb_server_data_t *srv, ilb_ip_addr_t *ip1, ilb_ip_addr_t *ip2) { struct in_addr *a1; ilbadm_servnode_t *sn_new; ilb_ip_addr_t new_ip; a1 = &ip1->ia_v4; new_ip.ia_af = AF_INET; new_ip.ia_v4 = i_next_in_addr(a1, 1); while (ilb_cmp_ipaddr(&new_ip, ip2, NULL) < 1) { sn_new = i_new_sg_elem(sg); sn_new->s_spec.sd_addr = new_ip; sn_new->s_spec.sd_minport = srv->sd_minport; sn_new->s_spec.sd_maxport = srv->sd_maxport; new_ip.ia_v4 = i_next_in_addr(&new_ip.ia_v4, 1); } return (ILBADM_OK); } static struct in6_addr i_next_in6_addr(struct in6_addr *a, int dir) { struct in6_addr ia6; uint64_t al, ah; ah = INV6_N2H_MSB64(a); al = INV6_N2H_LSB64(a); if (dir == 1) { /* overflow */ if (++al == 0) ah++; } else { /* underflow */ if (--al == 0xffffffff) ah--; } INV6_H2N_MSB64(&ia6, ah); INV6_H2N_LSB64(&ia6, al); return (ia6); } static ilbadm_status_t i_expand_ipv6range(ilbadm_sgroup_t *sg, ilb_server_data_t *srv, ilb_ip_addr_t *ip1, ilb_ip_addr_t *ip2) { struct in6_addr *a1; ilbadm_servnode_t *sn_new; ilb_ip_addr_t new_ip; a1 = &ip1->ia_v6; new_ip.ia_af = AF_INET6; new_ip.ia_v6 = i_next_in6_addr(a1, 1); while (ilb_cmp_ipaddr(&new_ip, ip2, NULL) < 1) { sn_new = i_new_sg_elem(sg); sn_new->s_spec.sd_addr = new_ip; sn_new->s_spec.sd_minport = srv->sd_minport; sn_new->s_spec.sd_maxport = srv->sd_maxport; new_ip.ia_v6 = i_next_in6_addr(&new_ip.ia_v6, 1); } return (ILBADM_OK); } /* * we create a list node in the servergroup for every ip address * in the range [ip1, ip2], where we interpret the ip addresses as * numbers * the first ip address is already stored in "sn" */ static ilbadm_status_t i_expand_iprange(ilbadm_sgroup_t *sg, ilb_server_data_t *sr, ilb_ip_addr_t *ip1, ilb_ip_addr_t *ip2) { int cmp; int64_t delta; if (ip2->ia_af == 0) return (ILBADM_OK); if (ip1->ia_af != ip2->ia_af) { ilbadm_err(gettext("IP address mismatch")); return (ILBADM_LIBERR); } /* if ip addresses are the same, we're done */ if ((cmp = ilb_cmp_ipaddr(ip1, ip2, &delta)) == 0) return (ILBADM_OK); if (cmp == 1) { ilbadm_err(gettext("starting IP address is must be less" " than ending ip address in ip range specification")); return (ILBADM_LIBERR); } /* if the implicit number of IPs is too large, stop */ if (abs((int)delta) > MAX_IP_SPREAD) return (ILBADM_TOOMANYIPADDR); switch (ip1->ia_af) { case AF_INET: return (i_expand_ipv4range(sg, sr, ip1, ip2)); case AF_INET6: return (i_expand_ipv6range(sg, sr, ip1, ip2)); } return (ILBADM_INVAL_AF); } /* * parse a port spec (number or by service name) and * return the numeric port in *host* byte order * * Upon return, *flags contains ILB_FLAGS_SRV_PORTNAME if a service name matches */ static int i_parseport(char *port, char *proto, int *flags) { struct servent *se; /* assumption: port names start with a non-digit */ if (isdigit(port[0])) { if (flags != NULL) *flags &= ~ILB_FLAGS_SRV_PORTNAME; return ((int)strtol(port, NULL, 10)); } se = getservbyname(port, proto); if (se == NULL) return (-1); if (flags != NULL) *flags |= ILB_FLAGS_SRV_PORTNAME; /* * we need to convert to host byte order to be in sync with * numerical ports. since result needs to be compared, this * is preferred to returning NW byte order */ return ((int)(ntohs(se->s_port))); } /* * matches one hostname or IP address and stores it in "store". * space must have been pre-allocated to accept data * "sg" != NULL only for cases where ip ranges may be coming in. */ static ilbadm_status_t i_match_hostorip(void *store, ilbadm_sgroup_t *sg, char *val, int flags, ilbadm_key_code_t keyword) { boolean_t is_ip_range_ok = flags & OPT_IP_RANGE; boolean_t is_addr_numeric = flags & OPT_NUMERIC_ONLY; boolean_t is_ports_ok = flags & OPT_PORTS; boolean_t ports_only = flags & OPT_PORTS_ONLY; boolean_t is_nat_src = flags & OPT_NAT; char *port_pref, *dash; char *port1p, *port2p, *host2p, *host1p; char *close1, *close2; ilb_ip_addr_t ip2store; ilb_ip_addr_t *ip1, *ip2; int p1, p2; ilb_server_data_t *s = NULL; ilbadm_status_t rc = ILBADM_OK; int af = AF_INET; addr_type_t at = 0; int p_flg; struct in6_addr v6nameaddr; port1p = port2p = host2p = host1p = NULL; port_pref = dash = NULL; close1 = close2 = NULL; errno = 0; if (is_nat_src) { ilb_rule_data_t *rd = (ilb_rule_data_t *)store; ip1 = &rd->r_nat_src_start; ip2 = &rd->r_nat_src_end; } else { ilbadm_servnode_t *sn = (ilbadm_servnode_t *)store; s = &sn->s_spec; ip1 = &s->sd_addr; ip2 = &ip2store; bzero(ip2, sizeof (*ip2)); } if (ports_only) { is_ports_ok = B_TRUE; port_pref = val - 1; /* we increment again later on */ goto ports; } /* * we parse the syntax ip[-ip][:port[-port]] * since IPv6 addresses contain ':'s as well, they need to be * enclosed in "[]" to be distinct from a potential port spec. * therefore, we need to first check whether we're dealing with * IPv6 addresses before we can go search for the port seperator * and ipv6 range could look like this: [ff::0]-[ff::255]:80 */ if ((keyword == ILB_KEY_SERVER) && (strchr(val, ':') != NULL) && (*val != '[') && ((inet_pton(AF_INET6, val, &v6nameaddr)) != 0)) { /* * V6 addresses must be enclosed within * brackets when specifying server addresses */ rc = ILBADM_INVAL_SYNTAX; goto err_out; } if (*val == '[') { af = AF_INET6; val++; host1p = val; close1 = strchr(val, (int)']'); if (close1 == NULL) { rc = ILBADM_INVAL_SYNTAX; goto err_out; } *close1 = '\0'; at = 0; rc = i_match_onehost(host1p, ip1, &at); if (rc != ILBADM_OK) goto err_out; if (at != numeric) { rc = ILBADM_INVAL_ADDR; goto err_out; } if (ip1->ia_af != af) { rc = ILBADM_INVAL_AF; goto err_out; } val = close1 + 1; if (*val == PORT_SEP) { port_pref = val; goto ports; } if (*val == '-') { dash = val; if (!is_ip_range_ok) { ilbadm_err(gettext("port ranges not allowed")); rc = ILBADM_LIBERR; goto err_out; } val++; if (*val != '[') { rc = ILBADM_INVAL_SYNTAX; goto err_out; } val++; close2 = strchr(val, (int)']'); if (close2 == NULL) { rc = ILBADM_INVAL_SYNTAX; goto err_out; } *close2 = '\0'; host2p = val; at = 0; rc = i_match_onehost(host2p, ip2, &at); if (rc != ILBADM_OK) goto err_out; if (at != numeric) { rc = ILBADM_INVAL_ADDR; goto err_out; } if (ip2->ia_af != af) { rc = ILBADM_INVAL_AF; goto err_out; } val = close2+1; } } /* ports always potentially allow ranges - XXXms: check? */ port_pref = strchr(val, (int)PORT_SEP); ports: if (port_pref != NULL && is_ports_ok) { port1p = port_pref + 1; *port_pref = '\0'; dash = strchr(port1p, (int)'-'); if (dash != NULL) { port2p = dash + 1; *dash = '\0'; } if (port1p != NULL) { p1 = i_parseport(port1p, NULL, &p_flg); if (p1 == -1 || p1 == 0 || p1 > ILB_MAX_PORT) { ilbadm_err(gettext("invalid port value %s" " specified"), port1p); rc = ILBADM_LIBERR; goto err_out; } s->sd_minport = htons((in_port_t)p1); if (p_flg & ILB_FLAGS_SRV_PORTNAME) s->sd_flags |= ILB_FLAGS_SRV_PORTNAME; } if (port2p != NULL) { /* ranges are only allowed for numeric ports */ if (p_flg & ILB_FLAGS_SRV_PORTNAME) { ilbadm_err(gettext("ranges are only allowed" " for numeric ports")); rc = ILBADM_LIBERR; goto err_out; } p2 = i_parseport(port2p, NULL, &p_flg); if (p2 == -1 || p2 <= p1 || p2 > ILB_MAX_PORT || (p_flg & ILB_FLAGS_SRV_PORTNAME) == ILB_FLAGS_SRV_PORTNAME) { ilbadm_err(gettext("invalid port value %s" " specified"), port2p); rc = ILBADM_LIBERR; goto err_out; } s->sd_maxport = htons((in_port_t)p2); } /* * we fill the '-' back in, but not the port seperator, * as the \0 in its place terminates the ip address(es) */ if (dash != NULL) *dash = '-'; if (ports_only) goto out; } if (af == AF_INET6) goto out; /* * we need to handle these situations for hosts: * a. ip address * b. ip address range (ip1-ip2) * c. a hostname (may include '-' or start with a digit) * * We want to do hostname lookup only if we're quite sure that * we actually are looking at neither a single IP address nor a * range of same, as this can hang if name service is not set up * (sth. likely in a LB environment). * * here's how we proceed: * 1. try to match numeric only. If that succeeds, we're done. * (getaddrinfo, which we call in i_match_onehost(), fails if * it encounters a '-') * 2. search for a '-'; if we find one, try numeric match for * both sides. if this fails: * 3. re-insert '-' and try for a legal hostname. */ /* 1. */ at = numeric; rc = i_match_onehost(val, ip1, &at); if (rc == ILBADM_OK) goto out; /* 2. */ dash = strchr(val, (int)'-'); if (dash != NULL && is_ip_range_ok) { host2p = dash + 1; *dash = '\0'; at = numeric; rc = i_match_onehost(host2p, ip2, &at); if (rc != ILBADM_OK || at != numeric) { *dash = '-'; dash = NULL; bzero(ip2, sizeof (*ip2)); goto hostname; } /* * if the RHS of '-' is an IP but LHS is not, we might * have a hostname of form x-y where y is just a number * (this seems a valid IPv4 address), so we need to * try a complete hostname */ rc = i_match_onehost(val, ip1, &at); if (rc != ILBADM_OK || at != numeric) { *dash = '-'; dash = NULL; goto hostname; } goto out; } hostname: /* 3. */ if (is_addr_numeric) at = numeric; else at = 0; rc = i_match_onehost(val, ip1, &at); if (rc != ILBADM_OK) { goto out; } if (s != NULL) { s->sd_flags |= ILB_FLAGS_SRV_HOSTNAME; /* XXX: todo: save hostname for re-display for admin */ } out: if (dash != NULL && !is_nat_src) { rc = i_expand_iprange(sg, s, ip1, ip2); if (rc != ILBADM_OK) goto err_out; } if (is_nat_src && host2p == NULL) *ip2 = *ip1; err_out: /* * we re-insert what we overwrote, especially in the error case */ if (close2 != NULL) *close2 = ']'; if (close1 != NULL) *close1 = '['; if (dash != NULL) *dash = '-'; if (port_pref != NULL && !ports_only) *port_pref = PORT_SEP; return (rc); } /* * type-agnostic helper function to return a pointer to a * pristine (and maybe freshly allocated) piece of storage * ready for something fitting "key" */ static void * i_new_storep(void *store, ilbadm_key_code_t key) { void *res; switch (key) { case ILB_KEY_SERVER: case ILB_KEY_SERVRANGE: case ILB_KEY_SERVERID: res = (void *) i_new_sg_elem(store); break; default: res = NULL; break; } return (res); } /* * make sure everything that needs to be there is there */ ilbadm_status_t i_check_rule_spec(ilb_rule_data_t *rd) { int32_t vip_af = rd->r_vip.ia_af; ilb_ip_addr_t *prxy_src; if (vip_af != AF_INET && vip_af != AF_INET6) return (ILBADM_INVAL_AF); if (*rd->r_sgname == '\0') return (ILBADM_ENOSGNAME); if (rd->r_algo == 0 || rd->r_topo == 0) { ilbadm_err(gettext("lbalg or type is unspecified")); return (ILBADM_LIBERR); } if (rd->r_topo == ILB_TOPO_NAT) { prxy_src = &rd->r_nat_src_start; if (prxy_src->ia_af != vip_af) { ilbadm_err(gettext("proxy-src is either missing" " or its address family does not" " match that of the VIP address")); return (ILBADM_LIBERR); } } /* extend as necessary */ return (ILBADM_OK); } /* * in parameter "sz" describes size (in bytes) of mask */ static int mask_to_prefixlen(const uchar_t *mask, const int sz) { uchar_t c; int i, j; int len = 0; int tmask; /* * for every byte in the mask, we start with most significant * bit and work our way down to the least significant bit; as * long as we find the bit set, we add 1 to the length. the * first unset bit we encounter terminates this process */ for (i = 0; i < sz; i++) { c = mask[i]; tmask = 1 << 7; for (j = 7; j >= 0; j--) { if ((c & tmask) == 0) return (len); len++; tmask >>= 1; } } return (len); } int ilbadm_mask_to_prefixlen(ilb_ip_addr_t *ip) { int af = ip->ia_af; int len = 0; assert(af == AF_INET || af == AF_INET6); switch (af) { case AF_INET: len = mask_to_prefixlen((uchar_t *)&ip->ia_v4.s_addr, sizeof (ip->ia_v4)); break; case AF_INET6: len = mask_to_prefixlen((uchar_t *)&ip->ia_v6.s6_addr, sizeof (ip->ia_v6)); break; } return (len); } /* copied from ifconfig.c, changed to return symbolic constants */ /* * Convert a prefix length to a mask. * Returns 1 if ok. 0 otherwise. * Assumes the mask array is zero'ed by the caller. */ static boolean_t in_prefixlentomask(int prefixlen, int maxlen, uchar_t *mask) { if (prefixlen < 0 || prefixlen > maxlen) return (B_FALSE); while (prefixlen > 0) { if (prefixlen >= 8) { *mask++ = 0xFF; prefixlen -= 8; continue; } *mask |= 1 << (8 - prefixlen); prefixlen--; } return (B_TRUE); } ilbadm_status_t ilbadm_set_netmask(char *val, ilb_ip_addr_t *ip, int af) { int prefixlen, maxval; boolean_t r; char *end; assert(af == AF_INET || af == AF_INET6); maxval = (af == AF_INET) ? 32 : 128; if (*val == '/') val++; prefixlen = strtol(val, &end, 10); if ((val == end) || (*end != '\0')) { ilbadm_err(gettext("invalid pmask provided")); return (ILBADM_LIBERR); } if (prefixlen < 1 || prefixlen > maxval) { ilbadm_err(gettext("invalid pmask provided (AF mismatch?)")); return (ILBADM_LIBERR); } switch (af) { case AF_INET: r = in_prefixlentomask(prefixlen, maxval, (uchar_t *)&ip->ia_v4.s_addr); break; case AF_INET6: r = in_prefixlentomask(prefixlen, maxval, (uchar_t *)&ip->ia_v6.s6_addr); break; } if (r != B_TRUE) { ilbadm_err(gettext("cannot convert %s to a netmask"), val); return (ILBADM_LIBERR); } ip->ia_af = af; return (ILBADM_OK); } static ilbadm_status_t i_store_val(char *val, void *store, ilbadm_key_code_t keyword) { ilbadm_status_t rc = ILBADM_OK; void *storep = store; ilb_rule_data_t *rd = NULL; ilbadm_sgroup_t *sg = NULL; ilb_hc_info_t *hc_info = NULL; struct protoent *pe; int64_t tmp_val; if (*val == '\0') return (ILBADM_NOKEYWORD_VAL); /* some types need new storage, others don't */ switch (keyword) { case ILB_KEY_SERVER: case ILB_KEY_SERVERID: sg = (ilbadm_sgroup_t *)store; storep = i_new_storep(store, keyword); break; case ILB_KEY_HEALTHCHECK: case ILB_KEY_SERVERGROUP: rd = (ilb_rule_data_t *)store; break; case ILB_KEY_VIP: /* fallthrough */ case ILB_KEY_PORT: /* fallthrough */ case ILB_KEY_HCPORT: /* fallthrough */ case ILB_KEY_CONNDRAIN: /* fallthrough */ case ILB_KEY_NAT_TO: /* fallthrough */ case ILB_KEY_STICKY_TO: /* fallthrough */ case ILB_KEY_PROTOCOL: /* fallthrough */ case ILB_KEY_ALGORITHM: /* fallthrough */ case ILB_KEY_STICKY: /* fallthrough */ case ILB_KEY_TYPE: /* fallthrough */ case ILB_KEY_SRC: /* fallthrough */ rd = (ilb_rule_data_t *)store; break; case ILB_KEY_HC_TEST: case ILB_KEY_HC_COUNT: case ILB_KEY_HC_INTERVAL: case ILB_KEY_HC_TIMEOUT: hc_info = (ilb_hc_info_t *)store; default: /* do nothing */ ; } switch (keyword) { case ILB_KEY_SRC: /* * the proxy-src keyword is only valid for full NAT topology * the value is either a single or a range of IP addresses. */ if (rd->r_topo != ILB_TOPO_NAT) { rc = ILBADM_INVAL_PROXY; break; } rc = i_match_hostorip(storep, sg, val, OPT_NUMERIC_ONLY | OPT_IP_RANGE | OPT_NAT, ILB_KEY_SRC); break; case ILB_KEY_SERVER: rc = i_match_hostorip(storep, sg, val, OPT_IP_RANGE | OPT_PORTS, ILB_KEY_SERVER); break; case ILB_KEY_SERVERID: if (val[0] != ILB_SRVID_PREFIX) rc = ILBADM_INVAL_SRVID; else rc = i_store_serverID(storep, val); break; case ILB_KEY_VIP: { ilb_ip_addr_t *vip = &rd->r_vip; addr_type_t at = numeric; char *close = NULL; /* * we duplicate some functionality of i_match_hostorip * here; that function is geared to mandate '[]' for IPv6 * addresses, which we want to relax here, so as not to * make i_match_hostorip even longer, we do what we need * here. */ if (*val == '[') { val++; if ((close = strchr(val, (int)']')) == NULL) { rc = ILBADM_INVAL_SYNTAX; break; } *close = NULL; } rc = i_match_onehost(val, vip, &at); /* re-assemble string as we found it */ if (close != NULL) { *close = ']'; if (rc == ILBADM_OK && vip->ia_af != AF_INET6) { ilbadm_err(gettext("use of '[]' only valid" " with IPv6 addresses")); rc = ILBADM_LIBERR; } } break; } case ILB_KEY_CONNDRAIN: tmp_val = strtoll(val, NULL, 10); if (tmp_val <= 0 || tmp_val > UINT_MAX) { rc = ILBADM_EINVAL; break; } rd->r_conndrain = tmp_val; break; case ILB_KEY_NAT_TO: tmp_val = strtoll(val, NULL, 10); if (tmp_val < 0 || tmp_val > UINT_MAX) { rc = ILBADM_EINVAL; break; } rd->r_nat_timeout = tmp_val; break; case ILB_KEY_STICKY_TO: tmp_val = strtoll(val, NULL, 10); if (tmp_val <= 0 || tmp_val > UINT_MAX) { rc = ILBADM_EINVAL; break; } rd->r_sticky_timeout = tmp_val; break; case ILB_KEY_PORT: if (isdigit(*val)) { ilbadm_servnode_t sn; bzero(&sn, sizeof (sn)); rc = i_match_hostorip((void *)&sn, sg, val, OPT_PORTS_ONLY, ILB_KEY_PORT); if (rc != ILBADM_OK) break; rd->r_minport = sn.s_spec.sd_minport; rd->r_maxport = sn.s_spec.sd_maxport; } else { struct servent *se; se = getservbyname(val, NULL); if (se == NULL) { rc = ILBADM_ENOSERVICE; break; } rd->r_minport = se->s_port; rd->r_maxport = 0; } break; case ILB_KEY_HCPORT: if (isdigit(*val)) { int hcport = atoi(val); if (hcport < 1 || hcport > 65535) { ilbadm_err(gettext("illegal number for" " hcport %s"), val); rc = ILBADM_LIBERR; break; } rd->r_hcport = htons(hcport); rd->r_hcpflag = ILB_HCI_PROBE_FIX; } else if (strcasecmp(val, "ANY") == 0) { rd->r_hcport = 0; rd->r_hcpflag = ILB_HCI_PROBE_ANY; } else { return (ILBADM_EINVAL); } break; case ILB_KEY_PROTOCOL: pe = getprotobyname(val); if (pe == NULL) rc = ILBADM_ENOPROTO; else rd->r_proto = pe->p_proto; break; case ILB_KEY_ALGORITHM: rd->r_algo = i_val_from_str(val, &algo_types[0]); if (rd->r_algo == ILBD_BAD_VAL) rc = ILBADM_INVAL_ALG; break; case ILB_KEY_STICKY: rd->r_flags |= ILB_FLAGS_RULE_STICKY; /* * CAVEAT: the use of r_vip.ia_af implies that the VIP * *must* be specified on the commandline *before* * the sticky mask. */ if (AF_UNSPEC == rd->r_vip.ia_af) { ilbadm_err(gettext("option '%s' requires that VIP be " "specified first"), ilbadm_key_to_opt(keyword)); rc = ILBADM_LIBERR; break; } rc = ilbadm_set_netmask(val, &rd->r_stickymask, rd->r_vip.ia_af); break; case ILB_KEY_TYPE: rd->r_topo = i_val_from_str(val, &topo_types[0]); if (rd->r_topo == ILBD_BAD_VAL) rc = ILBADM_INVAL_OPER; break; case ILB_KEY_SERVERGROUP: (void) strlcpy(rd->r_sgname, (char *)val, sizeof (rd->r_sgname)); break; case ILB_KEY_HEALTHCHECK: (void) strlcpy(rd->r_hcname, (char *)val, sizeof (rd->r_hcname)); break; case ILB_KEY_HC_TEST: (void) strlcpy(hc_info->hci_test, (char *)val, sizeof (hc_info->hci_test)); break; case ILB_KEY_HC_COUNT: if (isdigit(*val)) hc_info->hci_count = atoi(val); else return (ILBADM_EINVAL); break; case ILB_KEY_HC_INTERVAL: if (isdigit(*val)) hc_info->hci_interval = atoi(val); else return (ILBADM_EINVAL); break; case ILB_KEY_HC_TIMEOUT: if (isdigit(*val)) hc_info->hci_timeout = atoi(val); else return (ILBADM_EINVAL); break; default: rc = ILBADM_INVAL_KEYWORD; break; } return (rc); } /* * generic parsing function. * parses "key=value[,value]" strings in "arg". keylist determines the * list of valid keys in the LHS. keycode determines interpretation and * storage in store * XXXms: looks like "key=value[,value]" violates spec. needs a fix */ ilbadm_status_t i_parse_optstring(char *arg, void *store, ilbadm_key_name_t *keylist, int flags, int *count) { ilbadm_status_t rc = ILBADM_OK; char *comma = NULL, *equals = NULL; char *key, *nextkey, *val; ilbadm_key_code_t keyword; boolean_t is_value_list = flags & OPT_VALUE_LIST; boolean_t assign_seen = B_FALSE; int n; key = arg; n = 1; /* * Algorithm: * 1. find any commas indicating and seperating current value * from a following value * 2. if we're expecting a list of values (seperated by commas) * and have already seen the assignment, then * get the next "value" * 3. else (we're looking at the first element of the RHS) * 4. find the '=' * 5. match the keyword to the list we were passed in * 6. store the value. */ while (key != NULL && *key != '\0') { comma = equals = NULL; /* 2 */ nextkey = strchr(key, (int)','); if (nextkey != NULL) { comma = nextkey++; *comma = '\0'; } /* 3a */ if (is_value_list && assign_seen) { val = key; /* 3b */ } else { /* 4 */ equals = strchr(key, (int)'='); if (equals == NULL) { ilbadm_err("%s: %s", key, ilbadm_errstr(ILBADM_ASSIGNREQ)); rc = ILBADM_LIBERR; goto out; } val = equals + 1; *equals = '\0'; assign_seen = B_TRUE; /* 5 */ keyword = i_match_key(key, keylist); if (keyword == ILB_KEY_BAD) { ilbadm_err(gettext("bad keyword %s"), key); rc = ILBADM_LIBERR; goto out; } } /* 6 */ rc = i_store_val(val, store, keyword); if (rc != ILBADM_OK) { ilbadm_err("%s: %s", key, ilbadm_errstr(rc)); /* Change to ILBADM_ILBERR to avoid more err msgs. */ rc = ILBADM_LIBERR; goto out; } key = nextkey; n++; } out: if (comma != NULL) *comma = ','; if (equals != NULL) *equals = '='; if (count != NULL) *count = n; return (rc); }