dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / cmd / cmd-inet / usr.sbin / ilbadm / ilbadm_subr.c
blobd7f1c9b3b2ae9cc03608ea5eb92c7256def7e17e
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
19 * CDDL HEADER END
23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 * Copyright 2012 Milan Jurik. All rights reserved.
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <strings.h>
32 #include <sys/types.h>
33 #include <sys/socket.h>
34 #include <netinet/in.h>
35 #include <arpa/inet.h>
36 #include <netdb.h>
37 #include <errno.h>
38 #include <ctype.h>
39 #include <assert.h>
40 #include <limits.h>
41 #include <libilb.h>
42 #include <libilb_impl.h>
43 #include "ilbadm.h"
45 #define PORT_SEP ':'
47 typedef enum {
48 numeric = 1,
49 non_numeric
50 } addr_type_t;
52 ilbadm_val_type_t algo_types[] = {
53 {(int)ILB_ALG_ROUNDROBIN, "roundrobin", "rr"},
54 {(int)ILB_ALG_HASH_IP, "hash-ip", "hip"},
55 {(int)ILB_ALG_HASH_IP_SPORT, "hash-ip-port", "hipp"},
56 {(int)ILB_ALG_HASH_IP_VIP, "hash-ip-vip", "hipv"},
57 {ILBD_BAD_VAL, '\0', '\0'}
60 ilbadm_val_type_t topo_types[] = {
61 {(int)ILB_TOPO_DSR, "DSR", "d"},
62 {(int)ILB_TOPO_NAT, "NAT", "n"},
63 {(int)ILB_TOPO_HALF_NAT, "HALF-NAT", "h"},
64 {ILBD_BAD_VAL, '\0', '\0'}
67 void
68 ip2str(ilb_ip_addr_t *ip, char *buf, size_t sz, int flags)
70 int len;
72 switch (ip->ia_af) {
73 case AF_INET:
74 if (*(uint32_t *)&ip->ia_v4 == 0)
75 buf[0] = '\0';
76 else
77 (void) inet_ntop(AF_INET, (void *)&ip->ia_v4, buf, sz);
78 break;
79 case AF_INET6:
80 if (IN6_IS_ADDR_UNSPECIFIED(&ip->ia_v6)) {
81 buf[0] = '\0';
82 break;
84 if (!(flags & V6_ADDRONLY))
85 *buf++ = '[';
86 sz--;
87 (void) inet_ntop(ip->ia_af, (void *)&ip->ia_v6, buf, sz);
88 if (!(flags & V6_ADDRONLY)) {
89 len = strlen(buf);
90 buf[len] = ']';
91 buf[++len] = '\0';
93 break;
94 default: buf[0] = '\0';
98 char *
99 i_str_from_val(int val, ilbadm_val_type_t *types)
101 ilbadm_val_type_t *v;
103 for (v = types; v->v_type != ILBD_BAD_VAL; v++) {
104 if (v->v_type == val)
105 break;
107 /* we return this in all cases */
108 return (v->v_name);
112 i_val_from_str(char *name, ilbadm_val_type_t *types)
114 ilbadm_val_type_t *v;
116 for (v = types; v->v_type != ILBD_BAD_VAL; v++) {
117 if (strncasecmp(name, v->v_name, sizeof (v->v_name)) == 0 ||
118 strncasecmp(name, v->v_alias, sizeof (v->v_alias)) == 0)
119 break;
121 /* we return this in all cases */
122 return (v->v_type);
125 ilbadm_key_code_t
126 i_match_key(char *key, ilbadm_key_name_t *keylist)
128 ilbadm_key_name_t *t_key;
130 for (t_key = keylist; t_key->k_key != ILB_KEY_BAD; t_key++) {
131 if (strncasecmp(key, t_key->k_name,
132 sizeof (t_key->k_name)) == 0 ||
133 strncasecmp(key, t_key->k_alias,
134 sizeof (t_key->k_alias)) == 0)
135 break;
137 return (t_key->k_key);
141 * try to match:
142 * 1) IPv4 address
143 * 2) IPv6 address
144 * 3) a hostname
146 static ilbadm_status_t
147 i_match_onehost(const char *val, ilb_ip_addr_t *ip, addr_type_t *a_type)
149 struct addrinfo *ai = NULL;
150 struct addrinfo hints;
151 addr_type_t at = numeric;
153 (void) memset(&hints, 0, sizeof (hints));
154 hints.ai_flags |= AI_NUMERICHOST;
157 * if *a_type == numeric, we only want to check whether this
158 * is a (valid) numeric IP address. If we do and it is NOT,
159 * we return _ENOENT.
161 if (getaddrinfo(val, NULL, &hints, &ai) != 0) {
162 if (a_type != NULL && (*a_type == numeric))
163 return (ILBADM_INVAL_ADDR);
165 at = non_numeric;
166 if (getaddrinfo(val, NULL, NULL, &ai) != 0)
167 return (ILBADM_INVAL_ADDR);
170 ip->ia_af = ai->ai_family;
171 switch (ip->ia_af) {
172 case AF_INET: {
173 struct sockaddr_in sa;
175 assert(ai->ai_addrlen == sizeof (sa));
176 (void) memcpy(&sa, ai->ai_addr, sizeof (sa));
177 ip->ia_v4 = sa.sin_addr;
178 break;
180 case AF_INET6: {
181 struct sockaddr_in6 sa;
183 assert(ai->ai_addrlen == sizeof (sa));
184 (void) memcpy(&sa, ai->ai_addr, sizeof (sa));
185 ip->ia_v6 = sa.sin6_addr;
186 break;
188 default:
189 return (ILBADM_INVAL_AF);
192 if (a_type != NULL)
193 *a_type = at;
194 return (ILBADM_OK);
197 static ilbadm_status_t
198 i_store_serverID(void *store, char *val)
200 ilbadm_servnode_t *s = (ilbadm_servnode_t *)store;
201 ilb_server_data_t *sn = &s->s_spec;
204 * we shouldn't need to check for length here, as a name that's
205 * too long won't exist in the system anyway.
207 (void) strlcpy(sn->sd_srvID, val, sizeof (sn->sd_srvID));
208 return (ILBADM_OK);
211 static struct in_addr
212 i_next_in_addr(struct in_addr *a, int dir)
214 struct in_addr new_in;
215 uint32_t iah;
217 iah = ntohl(a->s_addr);
218 if (dir == 1)
219 iah++;
220 else
221 iah--;
222 new_in.s_addr = htonl(iah);
223 return (new_in);
226 static ilbadm_status_t
227 i_expand_ipv4range(ilbadm_sgroup_t *sg, ilb_server_data_t *srv,
228 ilb_ip_addr_t *ip1, ilb_ip_addr_t *ip2)
230 struct in_addr *a1;
231 ilbadm_servnode_t *sn_new;
232 ilb_ip_addr_t new_ip;
234 a1 = &ip1->ia_v4;
236 new_ip.ia_af = AF_INET;
237 new_ip.ia_v4 = i_next_in_addr(a1, 1);
238 while (ilb_cmp_ipaddr(&new_ip, ip2, NULL) < 1) {
239 sn_new = i_new_sg_elem(sg);
240 sn_new->s_spec.sd_addr = new_ip;
241 sn_new->s_spec.sd_minport = srv->sd_minport;
242 sn_new->s_spec.sd_maxport = srv->sd_maxport;
243 new_ip.ia_v4 = i_next_in_addr(&new_ip.ia_v4, 1);
245 return (ILBADM_OK);
248 static struct in6_addr
249 i_next_in6_addr(struct in6_addr *a, int dir)
251 struct in6_addr ia6;
252 uint64_t al, ah;
254 ah = INV6_N2H_MSB64(a);
255 al = INV6_N2H_LSB64(a);
257 if (dir == 1) {
258 /* overflow */
259 if (++al == 0)
260 ah++;
261 } else {
262 /* underflow */
263 if (--al == 0xffffffff)
264 ah--;
267 INV6_H2N_MSB64(&ia6, ah);
268 INV6_H2N_LSB64(&ia6, al);
269 return (ia6);
273 static ilbadm_status_t
274 i_expand_ipv6range(ilbadm_sgroup_t *sg, ilb_server_data_t *srv,
275 ilb_ip_addr_t *ip1, ilb_ip_addr_t *ip2)
277 struct in6_addr *a1;
278 ilbadm_servnode_t *sn_new;
279 ilb_ip_addr_t new_ip;
281 a1 = &ip1->ia_v6;
283 new_ip.ia_af = AF_INET6;
284 new_ip.ia_v6 = i_next_in6_addr(a1, 1);
285 while (ilb_cmp_ipaddr(&new_ip, ip2, NULL) < 1) {
286 sn_new = i_new_sg_elem(sg);
287 sn_new->s_spec.sd_addr = new_ip;
288 sn_new->s_spec.sd_minport = srv->sd_minport;
289 sn_new->s_spec.sd_maxport = srv->sd_maxport;
290 new_ip.ia_v6 = i_next_in6_addr(&new_ip.ia_v6, 1);
292 return (ILBADM_OK);
297 * we create a list node in the servergroup for every ip address
298 * in the range [ip1, ip2], where we interpret the ip addresses as
299 * numbers
300 * the first ip address is already stored in "sn"
302 static ilbadm_status_t
303 i_expand_iprange(ilbadm_sgroup_t *sg, ilb_server_data_t *sr,
304 ilb_ip_addr_t *ip1, ilb_ip_addr_t *ip2)
306 int cmp;
307 int64_t delta;
309 if (ip2->ia_af == 0)
310 return (ILBADM_OK);
312 if (ip1->ia_af != ip2->ia_af) {
313 ilbadm_err(gettext("IP address mismatch"));
314 return (ILBADM_LIBERR);
317 /* if ip addresses are the same, we're done */
318 if ((cmp = ilb_cmp_ipaddr(ip1, ip2, &delta)) == 0)
319 return (ILBADM_OK);
320 if (cmp == 1) {
321 ilbadm_err(gettext("starting IP address is must be less"
322 " than ending ip address in ip range specification"));
323 return (ILBADM_LIBERR);
326 /* if the implicit number of IPs is too large, stop */
327 if (abs((int)delta) > MAX_IP_SPREAD)
328 return (ILBADM_TOOMANYIPADDR);
330 switch (ip1->ia_af) {
331 case AF_INET:
332 return (i_expand_ipv4range(sg, sr, ip1, ip2));
333 case AF_INET6:
334 return (i_expand_ipv6range(sg, sr, ip1, ip2));
336 return (ILBADM_INVAL_AF);
340 * parse a port spec (number or by service name) and
341 * return the numeric port in *host* byte order
343 * Upon return, *flags contains ILB_FLAGS_SRV_PORTNAME if a service name matches
345 static int
346 i_parseport(char *port, char *proto, int *flags)
348 struct servent *se;
350 /* assumption: port names start with a non-digit */
351 if (isdigit(port[0])) {
352 if (flags != NULL)
353 *flags &= ~ILB_FLAGS_SRV_PORTNAME;
354 return ((int)strtol(port, NULL, 10));
357 se = getservbyname(port, proto);
358 if (se == NULL)
359 return (-1);
361 if (flags != NULL)
362 *flags |= ILB_FLAGS_SRV_PORTNAME;
365 * we need to convert to host byte order to be in sync with
366 * numerical ports. since result needs to be compared, this
367 * is preferred to returning NW byte order
369 return ((int)(ntohs(se->s_port)));
373 * matches one hostname or IP address and stores it in "store".
374 * space must have been pre-allocated to accept data
375 * "sg" != NULL only for cases where ip ranges may be coming in.
377 static ilbadm_status_t
378 i_match_hostorip(void *store, ilbadm_sgroup_t *sg, char *val,
379 int flags, ilbadm_key_code_t keyword)
381 boolean_t is_ip_range_ok = flags & OPT_IP_RANGE;
382 boolean_t is_addr_numeric = flags & OPT_NUMERIC_ONLY;
383 boolean_t is_ports_ok = flags & OPT_PORTS;
384 boolean_t ports_only = flags & OPT_PORTS_ONLY;
385 boolean_t is_nat_src = flags & OPT_NAT;
386 char *port_pref, *dash;
387 char *port1p, *port2p, *host2p, *host1p;
388 char *close1, *close2;
389 ilb_ip_addr_t ip2store;
390 ilb_ip_addr_t *ip1, *ip2;
391 int p1, p2;
392 ilb_server_data_t *s = NULL;
393 ilbadm_status_t rc = ILBADM_OK;
394 int af = AF_INET;
395 addr_type_t at = 0;
396 int p_flg;
397 struct in6_addr v6nameaddr;
399 port1p = port2p = host2p = host1p = NULL;
400 port_pref = dash = NULL;
401 close1 = close2 = NULL;
402 errno = 0;
404 if (is_nat_src) {
405 ilb_rule_data_t *rd = (ilb_rule_data_t *)store;
407 ip1 = &rd->r_nat_src_start;
408 ip2 = &rd->r_nat_src_end;
409 } else {
410 ilbadm_servnode_t *sn = (ilbadm_servnode_t *)store;
412 s = &sn->s_spec;
413 ip1 = &s->sd_addr;
414 ip2 = &ip2store;
415 bzero(ip2, sizeof (*ip2));
418 if (ports_only) {
419 is_ports_ok = B_TRUE;
420 port_pref = val - 1; /* we increment again later on */
421 goto ports;
425 * we parse the syntax ip[-ip][:port[-port]]
426 * since IPv6 addresses contain ':'s as well, they need to be
427 * enclosed in "[]" to be distinct from a potential port spec.
428 * therefore, we need to first check whether we're dealing with
429 * IPv6 addresses before we can go search for the port seperator
430 * and ipv6 range could look like this: [ff::0]-[ff::255]:80
432 if ((keyword == ILB_KEY_SERVER) && (strchr(val, ':') != NULL) &&
433 (*val != '[') && ((inet_pton(AF_INET6, val, &v6nameaddr)) != 0)) {
435 * V6 addresses must be enclosed within
436 * brackets when specifying server addresses
438 rc = ILBADM_INVAL_SYNTAX;
439 goto err_out;
442 if (*val == '[') {
443 af = AF_INET6;
445 val++;
446 host1p = val;
448 close1 = strchr(val, (int)']');
449 if (close1 == NULL) {
450 rc = ILBADM_INVAL_SYNTAX;
451 goto err_out;
453 *close1 = '\0';
454 at = 0;
455 rc = i_match_onehost(host1p, ip1, &at);
456 if (rc != ILBADM_OK)
457 goto err_out;
458 if (at != numeric) {
459 rc = ILBADM_INVAL_ADDR;
460 goto err_out;
462 if (ip1->ia_af != af) {
463 rc = ILBADM_INVAL_AF;
464 goto err_out;
466 val = close1 + 1;
468 if (*val == PORT_SEP) {
469 port_pref = val;
470 goto ports;
472 if (*val == '-') {
473 dash = val;
474 if (!is_ip_range_ok) {
475 ilbadm_err(gettext("port ranges not allowed"));
476 rc = ILBADM_LIBERR;
477 goto err_out;
479 val++;
480 if (*val != '[') {
481 rc = ILBADM_INVAL_SYNTAX;
482 goto err_out;
484 val++;
485 close2 = strchr(val, (int)']');
486 if (close2 == NULL) {
487 rc = ILBADM_INVAL_SYNTAX;
488 goto err_out;
490 *close2 = '\0';
491 host2p = val;
492 at = 0;
493 rc = i_match_onehost(host2p, ip2, &at);
494 if (rc != ILBADM_OK)
495 goto err_out;
496 if (at != numeric) {
497 rc = ILBADM_INVAL_ADDR;
498 goto err_out;
500 if (ip2->ia_af != af) {
501 rc = ILBADM_INVAL_AF;
502 goto err_out;
504 val = close2+1;
508 /* ports always potentially allow ranges - XXXms: check? */
509 port_pref = strchr(val, (int)PORT_SEP);
510 ports:
511 if (port_pref != NULL && is_ports_ok) {
512 port1p = port_pref + 1;
513 *port_pref = '\0';
515 dash = strchr(port1p, (int)'-');
516 if (dash != NULL) {
517 port2p = dash + 1;
518 *dash = '\0';
520 if (port1p != NULL) {
521 p1 = i_parseport(port1p, NULL, &p_flg);
522 if (p1 == -1 || p1 == 0 || p1 > ILB_MAX_PORT) {
523 ilbadm_err(gettext("invalid port value %s"
524 " specified"), port1p);
525 rc = ILBADM_LIBERR;
526 goto err_out;
528 s->sd_minport = htons((in_port_t)p1);
529 if (p_flg & ILB_FLAGS_SRV_PORTNAME)
530 s->sd_flags |= ILB_FLAGS_SRV_PORTNAME;
532 if (port2p != NULL) {
533 /* ranges are only allowed for numeric ports */
534 if (p_flg & ILB_FLAGS_SRV_PORTNAME) {
535 ilbadm_err(gettext("ranges are only allowed"
536 " for numeric ports"));
537 rc = ILBADM_LIBERR;
538 goto err_out;
540 p2 = i_parseport(port2p, NULL, &p_flg);
541 if (p2 == -1 || p2 <= p1 || p2 > ILB_MAX_PORT ||
542 (p_flg & ILB_FLAGS_SRV_PORTNAME) ==
543 ILB_FLAGS_SRV_PORTNAME) {
544 ilbadm_err(gettext("invalid port value %s"
545 " specified"), port2p);
546 rc = ILBADM_LIBERR;
547 goto err_out;
549 s->sd_maxport = htons((in_port_t)p2);
552 * we fill the '-' back in, but not the port seperator,
553 * as the \0 in its place terminates the ip address(es)
555 if (dash != NULL)
556 *dash = '-';
557 if (ports_only)
558 goto out;
561 if (af == AF_INET6)
562 goto out;
565 * we need to handle these situations for hosts:
566 * a. ip address
567 * b. ip address range (ip1-ip2)
568 * c. a hostname (may include '-' or start with a digit)
570 * We want to do hostname lookup only if we're quite sure that
571 * we actually are looking at neither a single IP address nor a
572 * range of same, as this can hang if name service is not set up
573 * (sth. likely in a LB environment).
575 * here's how we proceed:
576 * 1. try to match numeric only. If that succeeds, we're done.
577 * (getaddrinfo, which we call in i_match_onehost(), fails if
578 * it encounters a '-')
579 * 2. search for a '-'; if we find one, try numeric match for
580 * both sides. if this fails:
581 * 3. re-insert '-' and try for a legal hostname.
583 /* 1. */
584 at = numeric;
585 rc = i_match_onehost(val, ip1, &at);
586 if (rc == ILBADM_OK)
587 goto out;
589 /* 2. */
590 dash = strchr(val, (int)'-');
591 if (dash != NULL && is_ip_range_ok) {
592 host2p = dash + 1;
593 *dash = '\0';
594 at = numeric;
595 rc = i_match_onehost(host2p, ip2, &at);
596 if (rc != ILBADM_OK || at != numeric) {
597 *dash = '-';
598 dash = NULL;
599 bzero(ip2, sizeof (*ip2));
600 goto hostname;
603 * if the RHS of '-' is an IP but LHS is not, we might
604 * have a hostname of form x-y where y is just a number
605 * (this seems a valid IPv4 address), so we need to
606 * try a complete hostname
608 rc = i_match_onehost(val, ip1, &at);
609 if (rc != ILBADM_OK || at != numeric) {
610 *dash = '-';
611 dash = NULL;
612 goto hostname;
614 goto out;
616 hostname:
617 /* 3. */
619 if (is_addr_numeric)
620 at = numeric;
621 else
622 at = 0;
623 rc = i_match_onehost(val, ip1, &at);
624 if (rc != ILBADM_OK) {
625 goto out;
627 if (s != NULL) {
628 s->sd_flags |= ILB_FLAGS_SRV_HOSTNAME;
629 /* XXX: todo: save hostname for re-display for admin */
632 out:
633 if (dash != NULL && !is_nat_src) {
634 rc = i_expand_iprange(sg, s, ip1, ip2);
635 if (rc != ILBADM_OK)
636 goto err_out;
639 if (is_nat_src && host2p == NULL)
640 *ip2 = *ip1;
642 err_out:
644 * we re-insert what we overwrote, especially in the error case
646 if (close2 != NULL)
647 *close2 = ']';
648 if (close1 != NULL)
649 *close1 = '[';
650 if (dash != NULL)
651 *dash = '-';
652 if (port_pref != NULL && !ports_only)
653 *port_pref = PORT_SEP;
655 return (rc);
659 * type-agnostic helper function to return a pointer to a
660 * pristine (and maybe freshly allocated) piece of storage
661 * ready for something fitting "key"
663 static void *
664 i_new_storep(void *store, ilbadm_key_code_t key)
666 void *res;
668 switch (key) {
669 case ILB_KEY_SERVER:
670 case ILB_KEY_SERVRANGE:
671 case ILB_KEY_SERVERID:
672 res = (void *) i_new_sg_elem(store);
673 break;
674 default: res = NULL;
675 break;
678 return (res);
682 * make sure everything that needs to be there is there
684 ilbadm_status_t
685 i_check_rule_spec(ilb_rule_data_t *rd)
687 int32_t vip_af = rd->r_vip.ia_af;
688 ilb_ip_addr_t *prxy_src;
690 if (vip_af != AF_INET && vip_af != AF_INET6)
691 return (ILBADM_INVAL_AF);
693 if (*rd->r_sgname == '\0')
694 return (ILBADM_ENOSGNAME);
696 if (rd->r_algo == 0 || rd->r_topo == 0) {
697 ilbadm_err(gettext("lbalg or type is unspecified"));
698 return (ILBADM_LIBERR);
701 if (rd->r_topo == ILB_TOPO_NAT) {
702 prxy_src = &rd->r_nat_src_start;
703 if (prxy_src->ia_af != vip_af) {
704 ilbadm_err(gettext("proxy-src is either missing"
705 " or its address family does not"
706 " match that of the VIP address"));
707 return (ILBADM_LIBERR);
710 /* extend as necessary */
712 return (ILBADM_OK);
716 * in parameter "sz" describes size (in bytes) of mask
718 static int
719 mask_to_prefixlen(const uchar_t *mask, const int sz)
721 uchar_t c;
722 int i, j;
723 int len = 0;
724 int tmask;
727 * for every byte in the mask, we start with most significant
728 * bit and work our way down to the least significant bit; as
729 * long as we find the bit set, we add 1 to the length. the
730 * first unset bit we encounter terminates this process
732 for (i = 0; i < sz; i++) {
733 c = mask[i];
734 tmask = 1 << 7;
735 for (j = 7; j >= 0; j--) {
736 if ((c & tmask) == 0)
737 return (len);
738 len++;
739 tmask >>= 1;
742 return (len);
746 ilbadm_mask_to_prefixlen(ilb_ip_addr_t *ip)
748 int af = ip->ia_af;
749 int len = 0;
751 assert(af == AF_INET || af == AF_INET6);
752 switch (af) {
753 case AF_INET:
754 len = mask_to_prefixlen((uchar_t *)&ip->ia_v4.s_addr,
755 sizeof (ip->ia_v4));
756 break;
757 case AF_INET6:
758 len = mask_to_prefixlen((uchar_t *)&ip->ia_v6.s6_addr,
759 sizeof (ip->ia_v6));
760 break;
762 return (len);
765 /* copied from ifconfig.c, changed to return symbolic constants */
767 * Convert a prefix length to a mask.
768 * Returns 1 if ok. 0 otherwise.
769 * Assumes the mask array is zero'ed by the caller.
771 static boolean_t
772 in_prefixlentomask(int prefixlen, int maxlen, uchar_t *mask)
774 if (prefixlen < 0 || prefixlen > maxlen)
775 return (B_FALSE);
777 while (prefixlen > 0) {
778 if (prefixlen >= 8) {
779 *mask++ = 0xFF;
780 prefixlen -= 8;
781 continue;
783 *mask |= 1 << (8 - prefixlen);
784 prefixlen--;
786 return (B_TRUE);
789 ilbadm_status_t
790 ilbadm_set_netmask(char *val, ilb_ip_addr_t *ip, int af)
792 int prefixlen, maxval;
793 boolean_t r;
794 char *end;
796 assert(af == AF_INET || af == AF_INET6);
798 maxval = (af == AF_INET) ? 32 : 128;
800 if (*val == '/')
801 val++;
802 prefixlen = strtol(val, &end, 10);
803 if ((val == end) || (*end != '\0')) {
804 ilbadm_err(gettext("invalid pmask provided"));
805 return (ILBADM_LIBERR);
808 if (prefixlen < 1 || prefixlen > maxval) {
809 ilbadm_err(gettext("invalid pmask provided (AF mismatch?)"));
810 return (ILBADM_LIBERR);
813 switch (af) {
814 case AF_INET:
815 r = in_prefixlentomask(prefixlen, maxval,
816 (uchar_t *)&ip->ia_v4.s_addr);
817 break;
818 case AF_INET6:
819 r = in_prefixlentomask(prefixlen, maxval,
820 (uchar_t *)&ip->ia_v6.s6_addr);
821 break;
823 if (r != B_TRUE) {
824 ilbadm_err(gettext("cannot convert %s to a netmask"), val);
825 return (ILBADM_LIBERR);
827 ip->ia_af = af;
828 return (ILBADM_OK);
831 static ilbadm_status_t
832 i_store_val(char *val, void *store, ilbadm_key_code_t keyword)
834 ilbadm_status_t rc = ILBADM_OK;
835 void *storep = store;
836 ilb_rule_data_t *rd = NULL;
837 ilbadm_sgroup_t *sg = NULL;
838 ilb_hc_info_t *hc_info = NULL;
839 struct protoent *pe;
840 int64_t tmp_val;
842 if (*val == '\0')
843 return (ILBADM_NOKEYWORD_VAL);
845 /* some types need new storage, others don't */
846 switch (keyword) {
847 case ILB_KEY_SERVER:
848 case ILB_KEY_SERVERID:
849 sg = (ilbadm_sgroup_t *)store;
850 storep = i_new_storep(store, keyword);
851 break;
852 case ILB_KEY_HEALTHCHECK:
853 case ILB_KEY_SERVERGROUP:
854 rd = (ilb_rule_data_t *)store;
855 break;
856 case ILB_KEY_VIP: /* fallthrough */
857 case ILB_KEY_PORT: /* fallthrough */
858 case ILB_KEY_HCPORT: /* fallthrough */
859 case ILB_KEY_CONNDRAIN: /* fallthrough */
860 case ILB_KEY_NAT_TO: /* fallthrough */
861 case ILB_KEY_STICKY_TO: /* fallthrough */
862 case ILB_KEY_PROTOCOL: /* fallthrough */
863 case ILB_KEY_ALGORITHM: /* fallthrough */
864 case ILB_KEY_STICKY: /* fallthrough */
865 case ILB_KEY_TYPE: /* fallthrough */
866 case ILB_KEY_SRC: /* fallthrough */
867 rd = (ilb_rule_data_t *)store;
868 break;
869 case ILB_KEY_HC_TEST:
870 case ILB_KEY_HC_COUNT:
871 case ILB_KEY_HC_INTERVAL:
872 case ILB_KEY_HC_TIMEOUT:
873 hc_info = (ilb_hc_info_t *)store;
874 default: /* do nothing */
878 switch (keyword) {
879 case ILB_KEY_SRC:
881 * the proxy-src keyword is only valid for full NAT topology
882 * the value is either a single or a range of IP addresses.
884 if (rd->r_topo != ILB_TOPO_NAT) {
885 rc = ILBADM_INVAL_PROXY;
886 break;
888 rc = i_match_hostorip(storep, sg, val, OPT_NUMERIC_ONLY |
889 OPT_IP_RANGE | OPT_NAT, ILB_KEY_SRC);
890 break;
891 case ILB_KEY_SERVER:
892 rc = i_match_hostorip(storep, sg, val,
893 OPT_IP_RANGE | OPT_PORTS, ILB_KEY_SERVER);
894 break;
895 case ILB_KEY_SERVERID:
896 if (val[0] != ILB_SRVID_PREFIX)
897 rc = ILBADM_INVAL_SRVID;
898 else
899 rc = i_store_serverID(storep, val);
900 break;
901 case ILB_KEY_VIP: {
902 ilb_ip_addr_t *vip = &rd->r_vip;
903 addr_type_t at = numeric;
904 char *close = NULL;
907 * we duplicate some functionality of i_match_hostorip
908 * here; that function is geared to mandate '[]' for IPv6
909 * addresses, which we want to relax here, so as not to
910 * make i_match_hostorip even longer, we do what we need
911 * here.
913 if (*val == '[') {
914 val++;
915 if ((close = strchr(val, (int)']')) == NULL) {
916 rc = ILBADM_INVAL_SYNTAX;
917 break;
919 *close = '\0';
921 rc = i_match_onehost(val, vip, &at);
922 /* re-assemble string as we found it */
923 if (close != NULL) {
924 *close = ']';
925 if (rc == ILBADM_OK && vip->ia_af != AF_INET6) {
926 ilbadm_err(gettext("use of '[]' only valid"
927 " with IPv6 addresses"));
928 rc = ILBADM_LIBERR;
931 break;
933 case ILB_KEY_CONNDRAIN:
934 tmp_val = strtoll(val, NULL, 10);
935 if (tmp_val <= 0 || tmp_val > UINT_MAX) {
936 rc = ILBADM_EINVAL;
937 break;
939 rd->r_conndrain = tmp_val;
940 break;
941 case ILB_KEY_NAT_TO:
942 tmp_val = strtoll(val, NULL, 10);
943 if (tmp_val < 0 || tmp_val > UINT_MAX) {
944 rc = ILBADM_EINVAL;
945 break;
947 rd->r_nat_timeout = tmp_val;
948 break;
949 case ILB_KEY_STICKY_TO:
950 tmp_val = strtoll(val, NULL, 10);
951 if (tmp_val <= 0 || tmp_val > UINT_MAX) {
952 rc = ILBADM_EINVAL;
953 break;
955 rd->r_sticky_timeout = tmp_val;
956 break;
957 case ILB_KEY_PORT:
958 if (isdigit(*val)) {
959 ilbadm_servnode_t sn;
961 bzero(&sn, sizeof (sn));
962 rc = i_match_hostorip((void *)&sn, sg, val,
963 OPT_PORTS_ONLY, ILB_KEY_PORT);
964 if (rc != ILBADM_OK)
965 break;
966 rd->r_minport = sn.s_spec.sd_minport;
967 rd->r_maxport = sn.s_spec.sd_maxport;
968 } else {
969 struct servent *se;
971 se = getservbyname(val, NULL);
972 if (se == NULL) {
973 rc = ILBADM_ENOSERVICE;
974 break;
976 rd->r_minport = se->s_port;
977 rd->r_maxport = 0;
979 break;
980 case ILB_KEY_HCPORT:
981 if (isdigit(*val)) {
982 int hcport = atoi(val);
984 if (hcport < 1 || hcport > 65535) {
985 ilbadm_err(gettext("illegal number for"
986 " hcport %s"), val);
987 rc = ILBADM_LIBERR;
988 break;
990 rd->r_hcport = htons(hcport);
991 rd->r_hcpflag = ILB_HCI_PROBE_FIX;
992 } else if (strcasecmp(val, "ANY") == 0) {
993 rd->r_hcport = 0;
994 rd->r_hcpflag = ILB_HCI_PROBE_ANY;
995 } else {
996 return (ILBADM_EINVAL);
998 break;
999 case ILB_KEY_PROTOCOL:
1000 pe = getprotobyname(val);
1001 if (pe == NULL)
1002 rc = ILBADM_ENOPROTO;
1003 else
1004 rd->r_proto = pe->p_proto;
1005 break;
1006 case ILB_KEY_ALGORITHM:
1007 rd->r_algo = i_val_from_str(val, &algo_types[0]);
1008 if (rd->r_algo == ILBD_BAD_VAL)
1009 rc = ILBADM_INVAL_ALG;
1010 break;
1011 case ILB_KEY_STICKY:
1012 rd->r_flags |= ILB_FLAGS_RULE_STICKY;
1014 * CAVEAT: the use of r_vip.ia_af implies that the VIP
1015 * *must* be specified on the commandline *before*
1016 * the sticky mask.
1018 if (AF_UNSPEC == rd->r_vip.ia_af) {
1019 ilbadm_err(gettext("option '%s' requires that VIP be "
1020 "specified first"), ilbadm_key_to_opt(keyword));
1021 rc = ILBADM_LIBERR;
1022 break;
1024 rc = ilbadm_set_netmask(val, &rd->r_stickymask,
1025 rd->r_vip.ia_af);
1026 break;
1027 case ILB_KEY_TYPE:
1028 rd->r_topo = i_val_from_str(val, &topo_types[0]);
1029 if (rd->r_topo == ILBD_BAD_VAL)
1030 rc = ILBADM_INVAL_OPER;
1031 break;
1032 case ILB_KEY_SERVERGROUP:
1033 (void) strlcpy(rd->r_sgname, (char *)val,
1034 sizeof (rd->r_sgname));
1035 break;
1036 case ILB_KEY_HEALTHCHECK:
1037 (void) strlcpy(rd->r_hcname, (char *)val,
1038 sizeof (rd->r_hcname));
1039 break;
1040 case ILB_KEY_HC_TEST:
1041 (void) strlcpy(hc_info->hci_test, (char *)val,
1042 sizeof (hc_info->hci_test));
1043 break;
1044 case ILB_KEY_HC_COUNT:
1045 if (isdigit(*val))
1046 hc_info->hci_count = atoi(val);
1047 else
1048 return (ILBADM_EINVAL);
1049 break;
1050 case ILB_KEY_HC_INTERVAL:
1051 if (isdigit(*val))
1052 hc_info->hci_interval = atoi(val);
1053 else
1054 return (ILBADM_EINVAL);
1055 break;
1056 case ILB_KEY_HC_TIMEOUT:
1057 if (isdigit(*val))
1058 hc_info->hci_timeout = atoi(val);
1059 else
1060 return (ILBADM_EINVAL);
1061 break;
1062 default: rc = ILBADM_INVAL_KEYWORD;
1063 break;
1066 return (rc);
1070 * generic parsing function.
1071 * parses "key=value[,value]" strings in "arg". keylist determines the
1072 * list of valid keys in the LHS. keycode determines interpretation and
1073 * storage in store
1074 * XXXms: looks like "key=value[,value]" violates spec. needs a fix
1076 ilbadm_status_t
1077 i_parse_optstring(char *arg, void *store, ilbadm_key_name_t *keylist,
1078 int flags, int *count)
1080 ilbadm_status_t rc = ILBADM_OK;
1081 char *comma = NULL, *equals = NULL;
1082 char *key, *nextkey, *val;
1083 ilbadm_key_code_t keyword;
1084 boolean_t is_value_list = flags & OPT_VALUE_LIST;
1085 boolean_t assign_seen = B_FALSE;
1086 int n;
1088 key = arg;
1089 n = 1;
1091 * Algorithm:
1092 * 1. find any commas indicating and seperating current value
1093 * from a following value
1094 * 2. if we're expecting a list of values (seperated by commas)
1095 * and have already seen the assignment, then
1096 * get the next "value"
1097 * 3. else (we're looking at the first element of the RHS)
1098 * 4. find the '='
1099 * 5. match the keyword to the list we were passed in
1100 * 6. store the value.
1102 while (key != NULL && *key != '\0') {
1103 comma = equals = NULL;
1105 /* 2 */
1106 nextkey = strchr(key, (int)',');
1107 if (nextkey != NULL) {
1108 comma = nextkey++;
1109 *comma = '\0';
1112 /* 3a */
1113 if (is_value_list && assign_seen) {
1114 val = key;
1115 /* 3b */
1116 } else {
1117 /* 4 */
1118 equals = strchr(key, (int)'=');
1119 if (equals == NULL) {
1120 ilbadm_err("%s: %s", key,
1121 ilbadm_errstr(ILBADM_ASSIGNREQ));
1122 rc = ILBADM_LIBERR;
1123 goto out;
1125 val = equals + 1;
1126 *equals = '\0';
1127 assign_seen = B_TRUE;
1129 /* 5 */
1130 keyword = i_match_key(key, keylist);
1131 if (keyword == ILB_KEY_BAD) {
1132 ilbadm_err(gettext("bad keyword %s"), key);
1133 rc = ILBADM_LIBERR;
1134 goto out;
1138 /* 6 */
1139 rc = i_store_val(val, store, keyword);
1140 if (rc != ILBADM_OK) {
1141 ilbadm_err("%s: %s", key, ilbadm_errstr(rc));
1142 /* Change to ILBADM_ILBERR to avoid more err msgs. */
1143 rc = ILBADM_LIBERR;
1144 goto out;
1147 key = nextkey;
1148 n++;
1151 out:
1152 if (comma != NULL)
1153 *comma = ',';
1154 if (equals != NULL)
1155 *equals = '=';
1156 if (count != NULL)
1157 *count = n;
1158 return (rc);