xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm_subr.c (revision df3cd224ef765c29101e4110546062199562f757)
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  */
26 
27 #include <stdio.h>
28 #include <unistd.h>
29 #include <stdlib.h>
30 #include <strings.h>
31 #include <sys/types.h>
32 #include <sys/socket.h>
33 #include <netinet/in.h>
34 #include <arpa/inet.h>
35 #include <netdb.h>
36 #include <errno.h>
37 #include <ctype.h>
38 #include <assert.h>
39 #include <limits.h>
40 #include <libilb.h>
41 #include <libilb_impl.h>
42 #include "ilbadm.h"
43 
44 #define	PORT_SEP	':'
45 
46 typedef enum {
47 	numeric = 1,
48 	non_numeric
49 } addr_type_t;
50 
51 ilbadm_val_type_t algo_types[] = {
52 	{(int)ILB_ALG_ROUNDROBIN, "roundrobin", "rr"},
53 	{(int)ILB_ALG_HASH_IP, "hash-ip", "hip"},
54 	{(int)ILB_ALG_HASH_IP_SPORT, "hash-ip-port", "hipp"},
55 	{(int)ILB_ALG_HASH_IP_VIP, "hash-ip-vip", "hipv"},
56 	{ILBD_BAD_VAL, NULL, NULL}
57 };
58 
59 ilbadm_val_type_t topo_types[] = {
60 	{(int)ILB_TOPO_DSR, "DSR", "d"},
61 	{(int)ILB_TOPO_NAT, "NAT", "n"},
62 	{(int)ILB_TOPO_HALF_NAT, "HALF-NAT", "h"},
63 	{ILBD_BAD_VAL, NULL, NULL}
64 };
65 
66 void
67 ip2str(ilb_ip_addr_t *ip, char *buf, size_t sz, int flags)
68 {
69 	int	len;
70 
71 	switch (ip->ia_af) {
72 	case AF_INET:
73 		if (*(uint32_t *)&ip->ia_v4 == 0)
74 			buf[0] = '\0';
75 		else
76 			(void) inet_ntop(AF_INET, (void *)&ip->ia_v4, buf, sz);
77 		break;
78 	case AF_INET6:
79 		if (IN6_IS_ADDR_UNSPECIFIED(&ip->ia_v6)) {
80 			buf[0] = '\0';
81 			break;
82 		}
83 		if (!(flags & V6_ADDRONLY))
84 			*buf++ = '[';
85 		sz--;
86 		(void) inet_ntop(ip->ia_af, (void *)&ip->ia_v6, buf, sz);
87 		if (!(flags & V6_ADDRONLY)) {
88 			len = strlen(buf);
89 			buf[len] = ']';
90 			buf[++len] = '\0';
91 		}
92 		break;
93 	default: buf[0] = '\0';
94 	}
95 }
96 
97 char *
98 i_str_from_val(int val, ilbadm_val_type_t *types)
99 {
100 	ilbadm_val_type_t	*v;
101 
102 	for (v = types; v->v_type != ILBD_BAD_VAL; v++) {
103 		if (v->v_type == val)
104 			break;
105 	}
106 	/* we return this in all cases */
107 	return (v->v_name);
108 }
109 
110 int
111 i_val_from_str(char *name, ilbadm_val_type_t *types)
112 {
113 	ilbadm_val_type_t	*v;
114 
115 	for (v = types; v->v_type != ILBD_BAD_VAL; v++) {
116 		if (strncasecmp(name, v->v_name, sizeof (v->v_name)) == 0 ||
117 		    strncasecmp(name, v->v_alias, sizeof (v->v_alias)) == 0)
118 			break;
119 	}
120 	/* we return this in all cases */
121 	return (v->v_type);
122 }
123 
124 ilbadm_key_code_t
125 i_match_key(char *key, ilbadm_key_name_t *keylist)
126 {
127 	ilbadm_key_name_t	*t_key;
128 
129 	for (t_key = keylist; t_key->k_key != ILB_KEY_BAD; t_key++) {
130 		if (strncasecmp(key, t_key->k_name,
131 		    sizeof (t_key->k_name)) == 0 ||
132 		    strncasecmp(key, t_key->k_alias,
133 		    sizeof (t_key->k_alias)) == 0)
134 			break;
135 	}
136 	return (t_key->k_key);
137 }
138 
139 /*
140  * try to match:
141  * 1) IPv4 address
142  * 2) IPv6 address
143  * 3) a hostname
144  */
145 static ilbadm_status_t
146 i_match_onehost(const char *val, ilb_ip_addr_t *ip, addr_type_t *a_type)
147 {
148 	struct addrinfo *ai = NULL;
149 	struct addrinfo hints;
150 	addr_type_t	at = numeric;
151 
152 	(void) memset((void *)&hints, 0, sizeof (hints));
153 	hints.ai_flags |= AI_NUMERICHOST;
154 
155 	/*
156 	 * if *a_type == numeric, we only want to check whether this
157 	 * is a (valid) numeric IP address. If we do and it is NOT,
158 	 * we return _ENOENT.
159 	 */
160 	if (getaddrinfo(val, NULL, &hints, &ai) != 0) {
161 		if (a_type != NULL && (*a_type == numeric))
162 			return (ILBADM_INVAL_ADDR);
163 
164 		at = non_numeric;
165 		if (getaddrinfo(val, NULL, NULL, &ai) != 0)
166 			return (ILBADM_INVAL_ADDR);
167 	}
168 
169 	ip->ia_af = ai->ai_family;
170 	switch (ip->ia_af) {
171 	case AF_INET: {
172 		struct sockaddr_in	sa;
173 
174 		assert(ai->ai_addrlen == sizeof (sa));
175 		(void) memcpy(&sa, ai->ai_addr, sizeof (sa));
176 		ip->ia_v4 = sa.sin_addr;
177 		break;
178 	}
179 	case AF_INET6: {
180 		struct sockaddr_in6	sa;
181 
182 		assert(ai->ai_addrlen == sizeof (sa));
183 		(void) memcpy(&sa, ai->ai_addr, sizeof (sa));
184 		ip->ia_v6 = sa.sin6_addr;
185 		break;
186 	}
187 	default:
188 		return (ILBADM_INVAL_AF);
189 		break;
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: return (i_expand_ipv4range(sg, sr, ip1, ip2));
332 		/* not reached */
333 		break;
334 	case AF_INET6: return (i_expand_ipv6range(sg, sr, ip1, ip2));
335 		/* not reached */
336 		break;
337 	}
338 	return (ILBADM_INVAL_AF);
339 }
340 
341 /*
342  * parse a port spec (number or by service name) and
343  * return the numeric port in *host* byte order
344  *
345  * Upon return, *flags contains ILB_FLAGS_SRV_PORTNAME if a service name matches
346  */
347 static int
348 i_parseport(char *port, char *proto, int *flags)
349 {
350 	struct servent	*se;
351 
352 	/* assumption: port names start with a non-digit */
353 	if (isdigit(port[0])) {
354 		if (flags != NULL)
355 			*flags &= ~ILB_FLAGS_SRV_PORTNAME;
356 		return ((int)strtol(port, NULL, 10));
357 	}
358 
359 	se = getservbyname(port, proto);
360 	if (se == NULL)
361 		return (-1);
362 
363 	if (flags != NULL)
364 		*flags |= ILB_FLAGS_SRV_PORTNAME;
365 
366 	/*
367 	 * we need to convert to host byte order to be in sync with
368 	 * numerical ports. since result needs to be compared, this
369 	 * is preferred to returning NW byte order
370 	 */
371 	return ((int)(ntohs(se->s_port)));
372 }
373 
374 /*
375  * matches one hostname or IP address and stores it in "store".
376  * space must have been pre-allocated to accept data
377  * "sg" != NULL only for cases where ip ranges may be coming in.
378  */
379 static ilbadm_status_t
380 i_match_hostorip(void *store, ilbadm_sgroup_t *sg, char *val,
381     int flags, ilbadm_key_code_t keyword)
382 {
383 	boolean_t	is_ip_range_ok = flags & OPT_IP_RANGE;
384 	boolean_t	is_addr_numeric = flags & OPT_NUMERIC_ONLY;
385 	boolean_t	is_ports_ok = flags & OPT_PORTS;
386 	boolean_t	ports_only = flags & OPT_PORTS_ONLY;
387 	boolean_t	is_nat_src = flags & OPT_NAT;
388 	char		*port_pref, *dash;
389 	char		*port1p, *port2p, *host2p, *host1p;
390 	char		*close1, *close2;
391 	ilb_ip_addr_t	ip2store;
392 	ilb_ip_addr_t	*ip1, *ip2;
393 	int		p1, p2;
394 	ilb_server_data_t	*s = NULL;
395 	ilbadm_status_t	rc = ILBADM_OK;
396 	int		af = AF_INET;
397 	addr_type_t	at = 0;
398 	int		p_flg;
399 	struct in6_addr v6nameaddr;
400 
401 	port1p = port2p = host2p = host1p =  NULL;
402 	port_pref = dash = NULL;
403 	close1 = close2 = NULL;
404 	errno = 0;
405 
406 	if (is_nat_src) {
407 		ilb_rule_data_t *rd = (ilb_rule_data_t *)store;
408 
409 		ip1 = &rd->r_nat_src_start;
410 		ip2 = &rd->r_nat_src_end;
411 	} else {
412 		ilbadm_servnode_t *sn = (ilbadm_servnode_t *)store;
413 
414 		s = &sn->s_spec;
415 		ip1 = &s->sd_addr;
416 		ip2 = &ip2store;
417 		bzero(ip2, sizeof (*ip2));
418 	}
419 
420 	if (ports_only) {
421 		is_ports_ok = B_TRUE;
422 		port_pref = val - 1; /* we increment again later on */
423 		goto ports;
424 	}
425 
426 	/*
427 	 * we parse the syntax ip[-ip][:port[-port]]
428 	 * since IPv6 addresses contain ':'s as well, they need to be
429 	 * enclosed in "[]" to be distinct from a potential port spec.
430 	 * therefore, we need to first check whether we're dealing with
431 	 * IPv6 addresses before we can go search for the port seperator
432 	 * and ipv6 range could look like this: [ff::0]-[ff::255]:80
433 	 */
434 	if ((keyword == ILB_KEY_SERVER) && (strchr(val, ':') != NULL) &&
435 	    (*val != '[') && ((inet_pton(AF_INET6, val, &v6nameaddr)) != 0)) {
436 			/*
437 			 * V6 addresses must be enclosed within
438 			 * brackets when specifying server addresses
439 			 */
440 			rc = ILBADM_INVAL_SYNTAX;
441 			goto err_out;
442 	}
443 
444 	if (*val == '[') {
445 		af = AF_INET6;
446 
447 		val++;
448 		host1p = val;
449 
450 		close1 = strchr(val, (int)']');
451 		if (close1 == NULL) {
452 			rc = ILBADM_INVAL_SYNTAX;
453 			goto err_out;
454 		}
455 		*close1 = '\0';
456 		at = 0;
457 		rc = i_match_onehost(host1p, ip1, &at);
458 		if (rc != ILBADM_OK)
459 			goto err_out;
460 		if (at != numeric) {
461 			rc = ILBADM_INVAL_ADDR;
462 			goto err_out;
463 		}
464 		if (ip1->ia_af != af) {
465 			rc = ILBADM_INVAL_AF;
466 			goto err_out;
467 		}
468 		val = close1 + 1;
469 
470 		if (*val == PORT_SEP) {
471 			port_pref = val;
472 			goto ports;
473 		}
474 		if (*val == '-') {
475 			dash = val;
476 			if (!is_ip_range_ok) {
477 				ilbadm_err(gettext("port ranges not allowed"));
478 				rc = ILBADM_LIBERR;
479 				goto err_out;
480 			}
481 			val++;
482 			if (*val != '[') {
483 				rc = ILBADM_INVAL_SYNTAX;
484 				goto err_out;
485 			}
486 			val++;
487 			close2 = strchr(val, (int)']');
488 			if (close2 == NULL) {
489 				rc = ILBADM_INVAL_SYNTAX;
490 				goto err_out;
491 			}
492 			*close2 = '\0';
493 			host2p = val;
494 			at = 0;
495 			rc = i_match_onehost(host2p, ip2, &at);
496 			if (rc != ILBADM_OK)
497 				goto err_out;
498 			if (at != numeric) {
499 				rc = ILBADM_INVAL_ADDR;
500 				goto err_out;
501 			}
502 			if (ip2->ia_af != af) {
503 				rc = ILBADM_INVAL_AF;
504 				goto err_out;
505 			}
506 			val = close2+1;
507 		}
508 	}
509 
510 	/* ports always potentially allow ranges - XXXms: check? */
511 	port_pref = strchr(val, (int)PORT_SEP);
512 ports:
513 	if (port_pref != NULL && is_ports_ok) {
514 		port1p = port_pref + 1;
515 		*port_pref = '\0';
516 
517 		dash = strchr(port1p, (int)'-');
518 		if (dash != NULL) {
519 			port2p = dash + 1;
520 			*dash = '\0';
521 		}
522 		if (port1p != NULL) {
523 			p1 = i_parseport(port1p, NULL, &p_flg);
524 			if (p1 == -1 || p1 == 0 || p1 > ILB_MAX_PORT) {
525 				ilbadm_err(gettext("invalid port value %s"
526 				    " specified"), port1p);
527 				rc = ILBADM_LIBERR;
528 				goto err_out;
529 			}
530 			s->sd_minport = htons((in_port_t)p1);
531 			if (p_flg & ILB_FLAGS_SRV_PORTNAME)
532 				s->sd_flags |= ILB_FLAGS_SRV_PORTNAME;
533 		}
534 		if (port2p != NULL) {
535 			/* ranges are only allowed for numeric ports */
536 			if (p_flg & ILB_FLAGS_SRV_PORTNAME) {
537 				ilbadm_err(gettext("ranges are only allowed"
538 				    " for numeric ports"));
539 				rc = ILBADM_LIBERR;
540 				goto err_out;
541 			}
542 			p2 = i_parseport(port2p, NULL, &p_flg);
543 			if (p2 == -1 || p2 <= p1 || p2 > ILB_MAX_PORT ||
544 			    (p_flg & ILB_FLAGS_SRV_PORTNAME) ==
545 			    ILB_FLAGS_SRV_PORTNAME) {
546 				ilbadm_err(gettext("invalid port value %s"
547 				    " specified"), port2p);
548 				rc = ILBADM_LIBERR;
549 				goto err_out;
550 			}
551 			s->sd_maxport = htons((in_port_t)p2);
552 		}
553 		/*
554 		 * we fill the '-' back in, but not the port seperator,
555 		 * as the \0 in its place terminates the ip address(es)
556 		 */
557 		if (dash != NULL)
558 			*dash = '-';
559 		if (ports_only)
560 			goto out;
561 	}
562 
563 	if (af == AF_INET6)
564 		goto out;
565 
566 	/*
567 	 * we need to handle these situations for hosts:
568 	 *   a. ip address
569 	 *   b. ip address range (ip1-ip2)
570 	 *   c. a hostname (may include '-' or start with a digit)
571 	 *
572 	 * We want to do hostname lookup only if we're quite sure that
573 	 * we actually are looking at neither a single IP address nor a
574 	 * range of same, as this can hang if name service is not set up
575 	 * (sth. likely in a LB environment).
576 	 *
577 	 * here's how we proceed:
578 	 * 1. try to match numeric only. If that succeeds, we're done.
579 	 *    (getaddrinfo, which we call in i_match_onehost(), fails if
580 	 *    it encounters a '-')
581 	 * 2. search for a '-'; if we find one, try numeric match for
582 	 *    both sides. if this fails:
583 	 * 3. re-insert '-' and try for a legal hostname.
584 	 */
585 	/* 1. */
586 	at = numeric;
587 	rc = i_match_onehost(val, ip1, &at);
588 	if (rc == ILBADM_OK)
589 		goto out;
590 
591 	/* 2. */
592 	dash = strchr(val, (int)'-');
593 	if (dash != NULL && is_ip_range_ok) {
594 		host2p = dash + 1;
595 		*dash = '\0';
596 		at = numeric;
597 		rc = i_match_onehost(host2p, ip2, &at);
598 		if (rc != ILBADM_OK || at != numeric) {
599 			*dash = '-';
600 			dash = NULL;
601 			bzero(ip2, sizeof (*ip2));
602 			goto hostname;
603 		}
604 		/*
605 		 * if the RHS of '-' is an IP but LHS is not, we might
606 		 * have a hostname of form x-y where y is just a number
607 		 * (this seems a valid IPv4 address), so we need to
608 		 * try a complete hostname
609 		 */
610 		rc = i_match_onehost(val, ip1, &at);
611 		if (rc != ILBADM_OK || at != numeric) {
612 			*dash = '-';
613 			dash = NULL;
614 			goto hostname;
615 		}
616 		goto out;
617 	}
618 hostname:
619 	/* 3. */
620 
621 	if (is_addr_numeric)
622 		at = numeric;
623 	else
624 		at = 0;
625 	rc = i_match_onehost(val, ip1, &at);
626 	if (rc != ILBADM_OK) {
627 		goto out;
628 	}
629 	if (s != NULL) {
630 		s->sd_flags |= ILB_FLAGS_SRV_HOSTNAME;
631 		/* XXX: todo: save hostname for re-display for admin */
632 	}
633 
634 out:
635 	if (dash != NULL && !is_nat_src) {
636 		rc = i_expand_iprange(sg, s, ip1, ip2);
637 		if (rc != ILBADM_OK)
638 			goto err_out;
639 	}
640 
641 	if (is_nat_src && host2p == NULL)
642 		*ip2 = *ip1;
643 
644 err_out:
645 	/*
646 	 * we re-insert what we overwrote, especially in the error case
647 	 */
648 	if (close2 != NULL)
649 		*close2 = ']';
650 	if (close1 != NULL)
651 		*close1 = '[';
652 	if (dash != NULL)
653 		*dash = '-';
654 	if (port_pref != NULL && !ports_only)
655 		*port_pref = PORT_SEP;
656 
657 	return (rc);
658 }
659 
660 /*
661  * type-agnostic helper function to return a pointer to a
662  * pristine (and maybe freshly allocated) piece of storage
663  * ready for something fitting "key"
664  */
665 static void *
666 i_new_storep(void *store, ilbadm_key_code_t key)
667 {
668 	void	*res;
669 
670 	switch (key) {
671 	case ILB_KEY_SERVER:
672 	case ILB_KEY_SERVRANGE:
673 	case ILB_KEY_SERVERID:
674 		res = (void *) i_new_sg_elem(store);
675 		break;
676 	default: res = NULL;
677 		break;
678 	}
679 
680 	return (res);
681 }
682 
683 /*
684  * make sure everything that needs to be there is there
685  */
686 ilbadm_status_t
687 i_check_rule_spec(ilb_rule_data_t *rd)
688 {
689 	int32_t		vip_af = rd->r_vip.ia_af;
690 	ilb_ip_addr_t	*prxy_src;
691 
692 	if (vip_af != AF_INET && vip_af != AF_INET6)
693 		return (ILBADM_INVAL_AF);
694 
695 	if (*rd->r_sgname == '\0')
696 		return (ILBADM_ENOSGNAME);
697 
698 	if (rd->r_algo == 0 || rd->r_topo == 0) {
699 		ilbadm_err(gettext("lbalg or type is unspecified"));
700 		return (ILBADM_LIBERR);
701 	}
702 
703 	if (rd->r_topo == ILB_TOPO_NAT) {
704 		prxy_src = &rd->r_nat_src_start;
705 		if (prxy_src->ia_af != vip_af) {
706 			ilbadm_err(gettext("proxy-src is either missing"
707 			    " or its address family does not"
708 			    " match that of the VIP address"));
709 			return (ILBADM_LIBERR);
710 		}
711 	}
712 	/* extend as necessary */
713 
714 	return (ILBADM_OK);
715 }
716 
717 /*
718  * in parameter "sz" describes size (in bytes) of mask
719  */
720 static int
721 mask_to_prefixlen(const uchar_t *mask, const int sz)
722 {
723 	uchar_t	c;
724 	int	i, j;
725 	int	len = 0;
726 	int	tmask;
727 
728 	/*
729 	 * for every byte in the mask, we start with most significant
730 	 * bit and work our way down to the least significant bit; as
731 	 * long as we find the bit set, we add 1 to the length. the
732 	 * first unset bit we encounter terminates this process
733 	 */
734 	for (i = 0; i < sz; i++) {
735 		c = mask[i];
736 		tmask = 1 << 7;
737 		for (j = 7; j >= 0; j--) {
738 			if ((c & tmask) == 0)
739 				return (len);
740 			len++;
741 			tmask >>= 1;
742 		}
743 	}
744 	return (len);
745 }
746 
747 int
748 ilbadm_mask_to_prefixlen(ilb_ip_addr_t *ip)
749 {
750 	int af = ip->ia_af;
751 	int len = 0;
752 
753 	assert(af == AF_INET || af == AF_INET6);
754 	switch (af) {
755 	case AF_INET:
756 		len = mask_to_prefixlen((uchar_t *)&ip->ia_v4.s_addr,
757 		    sizeof (ip->ia_v4));
758 		break;
759 	case AF_INET6:
760 		len = mask_to_prefixlen((uchar_t *)&ip->ia_v6.s6_addr,
761 		    sizeof (ip->ia_v6));
762 		break;
763 	}
764 	return (len);
765 }
766 
767 /* copied from ifconfig.c, changed to return symbolic constants */
768 /*
769  * Convert a prefix length to a mask.
770  * Returns 1 if ok. 0 otherwise.
771  * Assumes the mask array is zero'ed by the caller.
772  */
773 static boolean_t
774 in_prefixlentomask(int prefixlen, int maxlen, uchar_t *mask)
775 {
776 	if (prefixlen < 0 || prefixlen > maxlen)
777 		return (B_FALSE);
778 
779 	while (prefixlen > 0) {
780 		if (prefixlen >= 8) {
781 			*mask++ = 0xFF;
782 			prefixlen -= 8;
783 			continue;
784 		}
785 		*mask |= 1 << (8 - prefixlen);
786 		prefixlen--;
787 	}
788 	return (B_TRUE);
789 }
790 
791 ilbadm_status_t
792 ilbadm_set_netmask(char *val, ilb_ip_addr_t *ip, int af)
793 {
794 	int	prefixlen, maxval;
795 	boolean_t	r;
796 	char	*end;
797 
798 	assert(af == AF_INET || af == AF_INET6);
799 
800 	maxval = (af == AF_INET) ? 32 : 128;
801 
802 	if (*val == '/')
803 		val++;
804 	prefixlen = strtol(val, &end, 10);
805 	if ((val == end) || (*end != '\0')) {
806 		ilbadm_err(gettext("invalid pmask provided"));
807 		return (ILBADM_LIBERR);
808 	}
809 
810 	if (prefixlen < 1 || prefixlen > maxval) {
811 		ilbadm_err(gettext("invalid pmask provided (AF mismatch?)"));
812 		return (ILBADM_LIBERR);
813 	}
814 
815 	switch (af) {
816 	case AF_INET:
817 		r = in_prefixlentomask(prefixlen, maxval,
818 		    (uchar_t *)&ip->ia_v4.s_addr);
819 		break;
820 	case AF_INET6:
821 		r = in_prefixlentomask(prefixlen, maxval,
822 		    (uchar_t *)&ip->ia_v6.s6_addr);
823 		break;
824 	}
825 	if (r != B_TRUE) {
826 		ilbadm_err(gettext("cannot convert %s to a netmask"), val);
827 		return (ILBADM_LIBERR);
828 	}
829 	ip->ia_af = af;
830 	return (ILBADM_OK);
831 }
832 
833 static ilbadm_status_t
834 i_store_val(char *val, void *store, ilbadm_key_code_t keyword)
835 {
836 	ilbadm_status_t	rc = ILBADM_OK;
837 	void		*storep = store;
838 	ilb_rule_data_t	*rd = NULL;
839 	ilbadm_sgroup_t	*sg = NULL;
840 	ilb_hc_info_t	*hc_info = NULL;
841 	struct protoent	*pe;
842 	int64_t		tmp_val;
843 
844 	if (*val == '\0')
845 		return (ILBADM_NOKEYWORD_VAL);
846 
847 	/* some types need new storage, others don't */
848 	switch (keyword) {
849 	case ILB_KEY_SERVER:
850 	case ILB_KEY_SERVERID:
851 		sg = (ilbadm_sgroup_t *)store;
852 		storep = i_new_storep(store, keyword);
853 		break;
854 	case ILB_KEY_HEALTHCHECK:
855 	case ILB_KEY_SERVERGROUP:
856 		rd = (ilb_rule_data_t *)store;
857 		break;
858 	case ILB_KEY_VIP:	/* fallthrough */
859 	case ILB_KEY_PORT:	/* fallthrough */
860 	case ILB_KEY_HCPORT:	/* fallthrough */
861 	case ILB_KEY_CONNDRAIN:	/* fallthrough */
862 	case ILB_KEY_NAT_TO:	/* fallthrough */
863 	case ILB_KEY_STICKY_TO:	/* fallthrough */
864 	case ILB_KEY_PROTOCOL:	/* fallthrough */
865 	case ILB_KEY_ALGORITHM:	/* fallthrough */
866 	case ILB_KEY_STICKY:	/* fallthrough */
867 	case ILB_KEY_TYPE:	/* fallthrough */
868 	case ILB_KEY_SRC:	/* fallthrough */
869 		rd = (ilb_rule_data_t *)store;
870 		break;
871 	case ILB_KEY_HC_TEST:
872 	case ILB_KEY_HC_COUNT:
873 	case ILB_KEY_HC_INTERVAL:
874 	case ILB_KEY_HC_TIMEOUT:
875 		hc_info = (ilb_hc_info_t *)store;
876 	default: /* do nothing */
877 		;
878 	}
879 
880 	switch (keyword) {
881 	case ILB_KEY_SRC:
882 		/*
883 		 * the proxy-src keyword is only valid for full NAT topology
884 		 * the value is either a single or a range of IP addresses.
885 		 */
886 		if (rd->r_topo != ILB_TOPO_NAT) {
887 			rc = ILBADM_INVAL_PROXY;
888 			break;
889 		}
890 		rc = i_match_hostorip(storep, sg, val, OPT_NUMERIC_ONLY |
891 		    OPT_IP_RANGE | OPT_NAT, ILB_KEY_SRC);
892 		break;
893 	case ILB_KEY_SERVER:
894 		rc = i_match_hostorip(storep, sg, val,
895 		    OPT_IP_RANGE | OPT_PORTS, ILB_KEY_SERVER);
896 		break;
897 	case ILB_KEY_SERVERID:
898 		if (val[0] != ILB_SRVID_PREFIX)
899 			rc = ILBADM_INVAL_SRVID;
900 		else
901 			rc = i_store_serverID(storep, val);
902 		break;
903 	case ILB_KEY_VIP: {
904 		ilb_ip_addr_t	*vip = &rd->r_vip;
905 		addr_type_t	at = numeric;
906 		char		*close = NULL;
907 
908 		/*
909 		 * we duplicate some functionality of i_match_hostorip
910 		 * here; that function is geared to mandate '[]' for IPv6
911 		 * addresses, which we want to relax here, so as not to
912 		 * make i_match_hostorip even longer, we do what we need
913 		 * here.
914 		 */
915 		if (*val == '[') {
916 			val++;
917 			if ((close = strchr(val, (int)']')) == NULL) {
918 				rc = ILBADM_INVAL_SYNTAX;
919 				break;
920 			}
921 			*close = NULL;
922 		}
923 		rc = i_match_onehost(val, vip, &at);
924 		/* re-assemble string as we found it */
925 		if (close != NULL) {
926 			*close = ']';
927 			if (rc == ILBADM_OK && vip->ia_af != AF_INET6) {
928 				ilbadm_err(gettext("use of '[]' only valid"
929 				    " with IPv6 addresses"));
930 				rc = ILBADM_LIBERR;
931 			}
932 		}
933 		break;
934 	}
935 	case ILB_KEY_CONNDRAIN:
936 		tmp_val = strtoll(val, NULL, 10);
937 		if (tmp_val <= 0 || tmp_val > UINT_MAX) {
938 			rc = ILBADM_EINVAL;
939 			break;
940 		}
941 		rd->r_conndrain = tmp_val;
942 		break;
943 	case ILB_KEY_NAT_TO:
944 		tmp_val = strtoll(val, NULL, 10);
945 		if (tmp_val < 0 || tmp_val > UINT_MAX) {
946 			rc = ILBADM_EINVAL;
947 			break;
948 		}
949 		rd->r_nat_timeout = tmp_val;
950 		break;
951 	case ILB_KEY_STICKY_TO:
952 		tmp_val = strtoll(val, NULL, 10);
953 		if (tmp_val <= 0 || tmp_val > UINT_MAX) {
954 			rc = ILBADM_EINVAL;
955 			break;
956 		}
957 		rd->r_sticky_timeout = tmp_val;
958 		break;
959 	case ILB_KEY_PORT:
960 		if (isdigit(*val)) {
961 			ilbadm_servnode_t	sn;
962 
963 			bzero(&sn, sizeof (sn));
964 			rc = i_match_hostorip((void *)&sn, sg, val,
965 			    OPT_PORTS_ONLY, ILB_KEY_PORT);
966 			if (rc != ILBADM_OK)
967 				break;
968 			rd->r_minport = sn.s_spec.sd_minport;
969 			rd->r_maxport = sn.s_spec.sd_maxport;
970 		} else {
971 			struct servent	*se;
972 
973 			se = getservbyname(val, NULL);
974 			if (se == NULL) {
975 				rc = ILBADM_ENOSERVICE;
976 				break;
977 			}
978 			rd->r_minport = se->s_port;
979 			rd->r_maxport = 0;
980 		}
981 		break;
982 	case ILB_KEY_HCPORT:
983 		if (isdigit(*val)) {
984 			int hcport = atoi(val);
985 
986 			if (hcport < 1 || hcport > 65535) {
987 				ilbadm_err(gettext("illegal number for"
988 				    " hcport %s"), val);
989 				rc = ILBADM_LIBERR;
990 				break;
991 			}
992 			rd->r_hcport = htons(hcport);
993 			rd->r_hcpflag = ILB_HCI_PROBE_FIX;
994 		} else if (strcasecmp(val, "ANY") == 0) {
995 			rd->r_hcport = 0;
996 			rd->r_hcpflag = ILB_HCI_PROBE_ANY;
997 		} else {
998 			return (ILBADM_EINVAL);
999 		}
1000 		break;
1001 	case ILB_KEY_PROTOCOL:
1002 		pe = getprotobyname(val);
1003 		if (pe == NULL)
1004 			rc = ILBADM_ENOPROTO;
1005 		else
1006 			rd->r_proto = pe->p_proto;
1007 		break;
1008 	case ILB_KEY_ALGORITHM:
1009 		rd->r_algo = i_val_from_str(val, &algo_types[0]);
1010 		if (rd->r_algo == ILBD_BAD_VAL)
1011 			rc = ILBADM_INVAL_ALG;
1012 		break;
1013 	case ILB_KEY_STICKY:
1014 		rd->r_flags |= ILB_FLAGS_RULE_STICKY;
1015 		/*
1016 		 * CAVEAT: the use of r_vip.ia_af implies that the VIP
1017 		 * *must* be specified on the commandline *before*
1018 		 * the sticky mask.
1019 		 */
1020 		if (AF_UNSPEC == rd->r_vip.ia_af) {
1021 			ilbadm_err(gettext("option '%s' requires that VIP be "
1022 			    "specified first"), ilbadm_key_to_opt(keyword));
1023 			rc = ILBADM_LIBERR;
1024 			break;
1025 		}
1026 		rc = ilbadm_set_netmask(val, &rd->r_stickymask,
1027 		    rd->r_vip.ia_af);
1028 		break;
1029 	case ILB_KEY_TYPE:
1030 		rd->r_topo = i_val_from_str(val, &topo_types[0]);
1031 		if (rd->r_topo == ILBD_BAD_VAL)
1032 			rc = ILBADM_INVAL_OPER;
1033 		break;
1034 	case ILB_KEY_SERVERGROUP:
1035 		(void) strlcpy(rd->r_sgname, (char *)val,
1036 		    sizeof (rd->r_sgname));
1037 		break;
1038 	case ILB_KEY_HEALTHCHECK:
1039 		(void) strlcpy(rd->r_hcname, (char *)val,
1040 		    sizeof (rd->r_hcname));
1041 		break;
1042 	case ILB_KEY_HC_TEST:
1043 		(void) strlcpy(hc_info->hci_test, (char *)val,
1044 		    sizeof (hc_info->hci_test));
1045 		break;
1046 	case ILB_KEY_HC_COUNT:
1047 		if (isdigit(*val))
1048 			hc_info->hci_count = atoi(val);
1049 		else
1050 			return (ILBADM_EINVAL);
1051 		break;
1052 	case ILB_KEY_HC_INTERVAL:
1053 		if (isdigit(*val))
1054 			hc_info->hci_interval = atoi(val);
1055 		else
1056 			return (ILBADM_EINVAL);
1057 		break;
1058 	case ILB_KEY_HC_TIMEOUT:
1059 		if (isdigit(*val))
1060 			hc_info->hci_timeout = atoi(val);
1061 		else
1062 			return (ILBADM_EINVAL);
1063 		break;
1064 	default: rc = ILBADM_INVAL_KEYWORD;
1065 		break;
1066 	}
1067 
1068 	return (rc);
1069 }
1070 
1071 /*
1072  * generic parsing function.
1073  * parses "key=value[,value]" strings in "arg". keylist determines the
1074  * list of valid keys in the LHS. keycode determines interpretation and
1075  * storage in store
1076  * XXXms: looks like "key=value[,value]" violates spec. needs a fix
1077  */
1078 ilbadm_status_t
1079 i_parse_optstring(char *arg, void *store, ilbadm_key_name_t *keylist,
1080     int flags, int *count)
1081 {
1082 	ilbadm_status_t	rc = ILBADM_OK;
1083 	char		*comma = NULL, *equals = NULL;
1084 	char		*key, *nextkey, *val;
1085 	ilbadm_key_code_t	keyword;
1086 	boolean_t	is_value_list = flags & OPT_VALUE_LIST;
1087 	boolean_t	assign_seen = B_FALSE;
1088 	int		n;
1089 
1090 	key = arg;
1091 	n = 1;
1092 	/*
1093 	 * Algorithm:
1094 	 * 1. find any commas indicating and seperating current value
1095 	 *    from a following value
1096 	 * 2. if we're expecting a list of values (seperated by commas)
1097 	 *	and have already seen the assignment, then
1098 	 *	get the next "value"
1099 	 * 3. else (we're looking at the first element of the RHS)
1100 	 *	4. find the '='
1101 	 *	5. match the keyword to the list we were passed in
1102 	 * 6. store the value.
1103 	 */
1104 	while (key != NULL && *key != '\0') {
1105 		comma = equals = NULL;
1106 
1107 		/* 2 */
1108 		nextkey = strchr(key, (int)',');
1109 		if (nextkey != NULL) {
1110 			comma = nextkey++;
1111 			*comma = '\0';
1112 		}
1113 
1114 		/* 3a */
1115 		if (is_value_list && assign_seen) {
1116 			val = key;
1117 		/* 3b */
1118 		} else {
1119 			/* 4 */
1120 			equals = strchr(key, (int)'=');
1121 			if (equals == NULL) {
1122 				ilbadm_err("%s: %s", key,
1123 				    ilbadm_errstr(ILBADM_ASSIGNREQ));
1124 				rc = ILBADM_LIBERR;
1125 				goto out;
1126 			}
1127 			val = equals + 1;
1128 			*equals = '\0';
1129 			assign_seen = B_TRUE;
1130 
1131 			/* 5 */
1132 			keyword = i_match_key(key, keylist);
1133 			if (keyword == ILB_KEY_BAD) {
1134 				ilbadm_err(gettext("bad keyword %s"), key);
1135 				rc = ILBADM_LIBERR;
1136 				goto out;
1137 			}
1138 		}
1139 
1140 		/* 6 */
1141 		rc = i_store_val(val, store, keyword);
1142 		if (rc != ILBADM_OK) {
1143 			ilbadm_err("%s: %s", key, ilbadm_errstr(rc));
1144 			/* Change to ILBADM_ILBERR to avoid more err msgs. */
1145 			rc = ILBADM_LIBERR;
1146 			goto out;
1147 		}
1148 
1149 		key = nextkey;
1150 		n++;
1151 	}
1152 
1153 out:
1154 	if (comma != NULL)
1155 		*comma = ',';
1156 	if (equals != NULL)
1157 		*equals = '=';
1158 	if (count != NULL)
1159 		*count = n;
1160 	return (rc);
1161 }
1162