xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm_subr.c (revision 5422785d352a2bb398daceab3d1898a8aa64d006)
1 /*
2  * CDDL HEADER START
3  *
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.
7  *
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.
12  *
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]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  * Copyright 2012 Milan Jurik. All rights reserved.
26  */
27 
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"
44 
45 #define	PORT_SEP	':'
46 
47 typedef enum {
48 	numeric = 1,
49 	non_numeric
50 } addr_type_t;
51 
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, NULL, NULL}
58 };
59 
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, NULL, NULL}
65 };
66 
67 void
68 ip2str(ilb_ip_addr_t *ip, char *buf, size_t sz, int flags)
69 {
70 	int	len;
71 
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;
83 		}
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';
92 		}
93 		break;
94 	default: buf[0] = '\0';
95 	}
96 }
97 
98 char *
99 i_str_from_val(int val, ilbadm_val_type_t *types)
100 {
101 	ilbadm_val_type_t	*v;
102 
103 	for (v = types; v->v_type != ILBD_BAD_VAL; v++) {
104 		if (v->v_type == val)
105 			break;
106 	}
107 	/* we return this in all cases */
108 	return (v->v_name);
109 }
110 
111 int
112 i_val_from_str(char *name, ilbadm_val_type_t *types)
113 {
114 	ilbadm_val_type_t	*v;
115 
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;
120 	}
121 	/* we return this in all cases */
122 	return (v->v_type);
123 }
124 
125 ilbadm_key_code_t
126 i_match_key(char *key, ilbadm_key_name_t *keylist)
127 {
128 	ilbadm_key_name_t	*t_key;
129 
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;
136 	}
137 	return (t_key->k_key);
138 }
139 
140 /*
141  * try to match:
142  * 1) IPv4 address
143  * 2) IPv6 address
144  * 3) a hostname
145  */
146 static ilbadm_status_t
147 i_match_onehost(const char *val, ilb_ip_addr_t *ip, addr_type_t *a_type)
148 {
149 	struct addrinfo *ai = NULL;
150 	struct addrinfo hints;
151 	addr_type_t	at = numeric;
152 
153 	(void) memset((void *)&hints, 0, sizeof (hints));
154 	hints.ai_flags |= AI_NUMERICHOST;
155 
156 	/*
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.
160 	 */
161 	if (getaddrinfo(val, NULL, &hints, &ai) != 0) {
162 		if (a_type != NULL && (*a_type == numeric))
163 			return (ILBADM_INVAL_ADDR);
164 
165 		at = non_numeric;
166 		if (getaddrinfo(val, NULL, NULL, &ai) != 0)
167 			return (ILBADM_INVAL_ADDR);
168 	}
169 
170 	ip->ia_af = ai->ai_family;
171 	switch (ip->ia_af) {
172 	case AF_INET: {
173 		struct sockaddr_in	sa;
174 
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;
179 	}
180 	case AF_INET6: {
181 		struct sockaddr_in6	sa;
182 
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;
187 	}
188 	default:
189 		return (ILBADM_INVAL_AF);
190 	}
191 
192 	if (a_type != NULL)
193 		*a_type = at;
194 	return (ILBADM_OK);
195 }
196 
197 static ilbadm_status_t
198 i_store_serverID(void *store, char *val)
199 {
200 	ilbadm_servnode_t	*s = (ilbadm_servnode_t *)store;
201 	ilb_server_data_t	*sn = &s->s_spec;
202 
203 	/*
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.
206 	 */
207 	(void) strlcpy(sn->sd_srvID, val, sizeof (sn->sd_srvID));
208 	return (ILBADM_OK);
209 }
210 
211 static struct in_addr
212 i_next_in_addr(struct in_addr *a, int dir)
213 {
214 	struct in_addr	new_in;
215 	uint32_t	iah;
216 
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);
224 }
225 
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)
229 {
230 	struct in_addr	*a1;
231 	ilbadm_servnode_t	*sn_new;
232 	ilb_ip_addr_t	new_ip;
233 
234 	a1 = &ip1->ia_v4;
235 
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);
244 	}
245 	return (ILBADM_OK);
246 }
247 
248 static struct in6_addr
249 i_next_in6_addr(struct in6_addr *a, int dir)
250 {
251 	struct in6_addr	ia6;
252 	uint64_t	al, ah;
253 
254 	ah = INV6_N2H_MSB64(a);
255 	al = INV6_N2H_LSB64(a);
256 
257 	if (dir == 1) {
258 		/* overflow */
259 		if (++al == 0)
260 			ah++;
261 	} else {
262 		/* underflow */
263 		if (--al == 0xffffffff)
264 			ah--;
265 	}
266 
267 	INV6_H2N_MSB64(&ia6, ah);
268 	INV6_H2N_LSB64(&ia6, al);
269 	return (ia6);
270 }
271 
272 
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)
276 {
277 	struct in6_addr	*a1;
278 	ilbadm_servnode_t	*sn_new;
279 	ilb_ip_addr_t	new_ip;
280 
281 	a1 = &ip1->ia_v6;
282 
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);
291 	}
292 	return (ILBADM_OK);
293 }
294 
295 
296 /*
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"
301  */
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)
305 {
306 	int		cmp;
307 	int64_t		delta;
308 
309 	if (ip2->ia_af == 0)
310 		return (ILBADM_OK);
311 
312 	if (ip1->ia_af != ip2->ia_af) {
313 		ilbadm_err(gettext("IP address mismatch"));
314 		return (ILBADM_LIBERR);
315 	}
316 
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);
324 	}
325 
326 	/* if the implicit number of IPs is too large, stop */
327 	if (abs((int)delta) > MAX_IP_SPREAD)
328 		return (ILBADM_TOOMANYIPADDR);
329 
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));
335 	}
336 	return (ILBADM_INVAL_AF);
337 }
338 
339 /*
340  * parse a port spec (number or by service name) and
341  * return the numeric port in *host* byte order
342  *
343  * Upon return, *flags contains ILB_FLAGS_SRV_PORTNAME if a service name matches
344  */
345 static int
346 i_parseport(char *port, char *proto, int *flags)
347 {
348 	struct servent	*se;
349 
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));
355 	}
356 
357 	se = getservbyname(port, proto);
358 	if (se == NULL)
359 		return (-1);
360 
361 	if (flags != NULL)
362 		*flags |= ILB_FLAGS_SRV_PORTNAME;
363 
364 	/*
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
368 	 */
369 	return ((int)(ntohs(se->s_port)));
370 }
371 
372 /*
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.
376  */
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)
380 {
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;
398 
399 	port1p = port2p = host2p = host1p =  NULL;
400 	port_pref = dash = NULL;
401 	close1 = close2 = NULL;
402 	errno = 0;
403 
404 	if (is_nat_src) {
405 		ilb_rule_data_t *rd = (ilb_rule_data_t *)store;
406 
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;
411 
412 		s = &sn->s_spec;
413 		ip1 = &s->sd_addr;
414 		ip2 = &ip2store;
415 		bzero(ip2, sizeof (*ip2));
416 	}
417 
418 	if (ports_only) {
419 		is_ports_ok = B_TRUE;
420 		port_pref = val - 1; /* we increment again later on */
421 		goto ports;
422 	}
423 
424 	/*
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
431 	 */
432 	if ((keyword == ILB_KEY_SERVER) && (strchr(val, ':') != NULL) &&
433 	    (*val != '[') && ((inet_pton(AF_INET6, val, &v6nameaddr)) != 0)) {
434 			/*
435 			 * V6 addresses must be enclosed within
436 			 * brackets when specifying server addresses
437 			 */
438 			rc = ILBADM_INVAL_SYNTAX;
439 			goto err_out;
440 	}
441 
442 	if (*val == '[') {
443 		af = AF_INET6;
444 
445 		val++;
446 		host1p = val;
447 
448 		close1 = strchr(val, (int)']');
449 		if (close1 == NULL) {
450 			rc = ILBADM_INVAL_SYNTAX;
451 			goto err_out;
452 		}
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;
461 		}
462 		if (ip1->ia_af != af) {
463 			rc = ILBADM_INVAL_AF;
464 			goto err_out;
465 		}
466 		val = close1 + 1;
467 
468 		if (*val == PORT_SEP) {
469 			port_pref = val;
470 			goto ports;
471 		}
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;
478 			}
479 			val++;
480 			if (*val != '[') {
481 				rc = ILBADM_INVAL_SYNTAX;
482 				goto err_out;
483 			}
484 			val++;
485 			close2 = strchr(val, (int)']');
486 			if (close2 == NULL) {
487 				rc = ILBADM_INVAL_SYNTAX;
488 				goto err_out;
489 			}
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;
499 			}
500 			if (ip2->ia_af != af) {
501 				rc = ILBADM_INVAL_AF;
502 				goto err_out;
503 			}
504 			val = close2+1;
505 		}
506 	}
507 
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';
514 
515 		dash = strchr(port1p, (int)'-');
516 		if (dash != NULL) {
517 			port2p = dash + 1;
518 			*dash = '\0';
519 		}
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;
527 			}
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;
531 		}
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;
539 			}
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;
548 			}
549 			s->sd_maxport = htons((in_port_t)p2);
550 		}
551 		/*
552 		 * we fill the '-' back in, but not the port seperator,
553 		 * as the \0 in its place terminates the ip address(es)
554 		 */
555 		if (dash != NULL)
556 			*dash = '-';
557 		if (ports_only)
558 			goto out;
559 	}
560 
561 	if (af == AF_INET6)
562 		goto out;
563 
564 	/*
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)
569 	 *
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).
574 	 *
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.
582 	 */
583 	/* 1. */
584 	at = numeric;
585 	rc = i_match_onehost(val, ip1, &at);
586 	if (rc == ILBADM_OK)
587 		goto out;
588 
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;
601 		}
602 		/*
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
607 		 */
608 		rc = i_match_onehost(val, ip1, &at);
609 		if (rc != ILBADM_OK || at != numeric) {
610 			*dash = '-';
611 			dash = NULL;
612 			goto hostname;
613 		}
614 		goto out;
615 	}
616 hostname:
617 	/* 3. */
618 
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;
626 	}
627 	if (s != NULL) {
628 		s->sd_flags |= ILB_FLAGS_SRV_HOSTNAME;
629 		/* XXX: todo: save hostname for re-display for admin */
630 	}
631 
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;
637 	}
638 
639 	if (is_nat_src && host2p == NULL)
640 		*ip2 = *ip1;
641 
642 err_out:
643 	/*
644 	 * we re-insert what we overwrote, especially in the error case
645 	 */
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;
654 
655 	return (rc);
656 }
657 
658 /*
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"
662  */
663 static void *
664 i_new_storep(void *store, ilbadm_key_code_t key)
665 {
666 	void	*res;
667 
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;
676 	}
677 
678 	return (res);
679 }
680 
681 /*
682  * make sure everything that needs to be there is there
683  */
684 ilbadm_status_t
685 i_check_rule_spec(ilb_rule_data_t *rd)
686 {
687 	int32_t		vip_af = rd->r_vip.ia_af;
688 	ilb_ip_addr_t	*prxy_src;
689 
690 	if (vip_af != AF_INET && vip_af != AF_INET6)
691 		return (ILBADM_INVAL_AF);
692 
693 	if (*rd->r_sgname == '\0')
694 		return (ILBADM_ENOSGNAME);
695 
696 	if (rd->r_algo == 0 || rd->r_topo == 0) {
697 		ilbadm_err(gettext("lbalg or type is unspecified"));
698 		return (ILBADM_LIBERR);
699 	}
700 
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);
708 		}
709 	}
710 	/* extend as necessary */
711 
712 	return (ILBADM_OK);
713 }
714 
715 /*
716  * in parameter "sz" describes size (in bytes) of mask
717  */
718 static int
719 mask_to_prefixlen(const uchar_t *mask, const int sz)
720 {
721 	uchar_t	c;
722 	int	i, j;
723 	int	len = 0;
724 	int	tmask;
725 
726 	/*
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
731 	 */
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;
740 		}
741 	}
742 	return (len);
743 }
744 
745 int
746 ilbadm_mask_to_prefixlen(ilb_ip_addr_t *ip)
747 {
748 	int af = ip->ia_af;
749 	int len = 0;
750 
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;
761 	}
762 	return (len);
763 }
764 
765 /* copied from ifconfig.c, changed to return symbolic constants */
766 /*
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.
770  */
771 static boolean_t
772 in_prefixlentomask(int prefixlen, int maxlen, uchar_t *mask)
773 {
774 	if (prefixlen < 0 || prefixlen > maxlen)
775 		return (B_FALSE);
776 
777 	while (prefixlen > 0) {
778 		if (prefixlen >= 8) {
779 			*mask++ = 0xFF;
780 			prefixlen -= 8;
781 			continue;
782 		}
783 		*mask |= 1 << (8 - prefixlen);
784 		prefixlen--;
785 	}
786 	return (B_TRUE);
787 }
788 
789 ilbadm_status_t
790 ilbadm_set_netmask(char *val, ilb_ip_addr_t *ip, int af)
791 {
792 	int	prefixlen, maxval;
793 	boolean_t	r;
794 	char	*end;
795 
796 	assert(af == AF_INET || af == AF_INET6);
797 
798 	maxval = (af == AF_INET) ? 32 : 128;
799 
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);
806 	}
807 
808 	if (prefixlen < 1 || prefixlen > maxval) {
809 		ilbadm_err(gettext("invalid pmask provided (AF mismatch?)"));
810 		return (ILBADM_LIBERR);
811 	}
812 
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;
822 	}
823 	if (r != B_TRUE) {
824 		ilbadm_err(gettext("cannot convert %s to a netmask"), val);
825 		return (ILBADM_LIBERR);
826 	}
827 	ip->ia_af = af;
828 	return (ILBADM_OK);
829 }
830 
831 static ilbadm_status_t
832 i_store_val(char *val, void *store, ilbadm_key_code_t keyword)
833 {
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;
841 
842 	if (*val == '\0')
843 		return (ILBADM_NOKEYWORD_VAL);
844 
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 */
875 		;
876 	}
877 
878 	switch (keyword) {
879 	case ILB_KEY_SRC:
880 		/*
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.
883 		 */
884 		if (rd->r_topo != ILB_TOPO_NAT) {
885 			rc = ILBADM_INVAL_PROXY;
886 			break;
887 		}
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;
905 
906 		/*
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.
912 		 */
913 		if (*val == '[') {
914 			val++;
915 			if ((close = strchr(val, (int)']')) == NULL) {
916 				rc = ILBADM_INVAL_SYNTAX;
917 				break;
918 			}
919 			*close = NULL;
920 		}
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;
929 			}
930 		}
931 		break;
932 	}
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;
938 		}
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;
946 		}
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;
954 		}
955 		rd->r_sticky_timeout = tmp_val;
956 		break;
957 	case ILB_KEY_PORT:
958 		if (isdigit(*val)) {
959 			ilbadm_servnode_t	sn;
960 
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;
970 
971 			se = getservbyname(val, NULL);
972 			if (se == NULL) {
973 				rc = ILBADM_ENOSERVICE;
974 				break;
975 			}
976 			rd->r_minport = se->s_port;
977 			rd->r_maxport = 0;
978 		}
979 		break;
980 	case ILB_KEY_HCPORT:
981 		if (isdigit(*val)) {
982 			int hcport = atoi(val);
983 
984 			if (hcport < 1 || hcport > 65535) {
985 				ilbadm_err(gettext("illegal number for"
986 				    " hcport %s"), val);
987 				rc = ILBADM_LIBERR;
988 				break;
989 			}
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);
997 		}
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;
1013 		/*
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.
1017 		 */
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;
1023 		}
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;
1064 	}
1065 
1066 	return (rc);
1067 }
1068 
1069 /*
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
1075  */
1076 ilbadm_status_t
1077 i_parse_optstring(char *arg, void *store, ilbadm_key_name_t *keylist,
1078     int flags, int *count)
1079 {
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;
1087 
1088 	key = arg;
1089 	n = 1;
1090 	/*
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.
1101 	 */
1102 	while (key != NULL && *key != '\0') {
1103 		comma = equals = NULL;
1104 
1105 		/* 2 */
1106 		nextkey = strchr(key, (int)',');
1107 		if (nextkey != NULL) {
1108 			comma = nextkey++;
1109 			*comma = '\0';
1110 		}
1111 
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;
1124 			}
1125 			val = equals + 1;
1126 			*equals = '\0';
1127 			assign_seen = B_TRUE;
1128 
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;
1135 			}
1136 		}
1137 
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;
1145 		}
1146 
1147 		key = nextkey;
1148 		n++;
1149 	}
1150 
1151 out:
1152 	if (comma != NULL)
1153 		*comma = ',';
1154 	if (equals != NULL)
1155 		*equals = '=';
1156 	if (count != NULL)
1157 		*count = n;
1158 	return (rc);
1159 }
1160