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
47typedef enum {
48	numeric = 1,
49	non_numeric
50} addr_type_t;
51
52ilbadm_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}
58};
59
60ilbadm_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}
65};
66
67void
68ip2str(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
98char *
99i_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
111int
112i_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
125ilbadm_key_code_t
126i_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 */
146static ilbadm_status_t
147i_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
197static ilbadm_status_t
198i_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
211static struct in_addr
212i_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
226static ilbadm_status_t
227i_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
248static struct in6_addr
249i_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
273static ilbadm_status_t
274i_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 */
302static ilbadm_status_t
303i_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 */
345static int
346i_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 */
377static ilbadm_status_t
378i_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);
510ports:
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	}
616hostname:
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
632out:
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
642err_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 */
663static void *
664i_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 */
684ilbadm_status_t
685i_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 */
718static int
719mask_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
745int
746ilbadm_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 */
771static boolean_t
772in_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
789ilbadm_status_t
790ilbadm_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
831static ilbadm_status_t
832i_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 = '\0';
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 */
1076ilbadm_status_t
1077i_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
1151out:
1152	if (comma != NULL)
1153		*comma = ',';
1154	if (equals != NULL)
1155		*equals = '=';
1156	if (count != NULL)
1157		*count = n;
1158	return (rc);
1159}
1160