/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1991, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 1990 Mentat Inc.
 * Copyright (c) 2013 by Delphix. All rights reserved.
 */

#include <inet/tunables.h>
#include <sys/md5.h>
#include <inet/common.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <netinet/icmp6.h>
#include <inet/ip_stack.h>
#include <inet/rawip_impl.h>
#include <inet/tcp_stack.h>
#include <inet/tcp_impl.h>
#include <inet/udp_impl.h>
#include <inet/sctp/sctp_stack.h>
#include <inet/sctp/sctp_impl.h>
#include <inet/tunables.h>

mod_prop_info_t *
mod_prop_lookup(mod_prop_info_t ptbl[], const char *prop_name, uint_t proto)
{
	mod_prop_info_t *pinfo;

	/*
	 * Walk the ptbl array looking for a property that has the requested
	 * name and protocol number.  Note that we assume that all protocol
	 * tables are terminated by an entry with a NULL property name.
	 */
	for (pinfo = ptbl; pinfo->mpi_name != NULL; pinfo++) {
		if (strcmp(pinfo->mpi_name, prop_name) == 0 &&
		    pinfo->mpi_proto == proto)
			return (pinfo);
	}
	return (NULL);
}

static int
prop_perm2const(mod_prop_info_t *pinfo)
{
	if (pinfo->mpi_setf == NULL)
		return (MOD_PROP_PERM_READ);
	if (pinfo->mpi_getf == NULL)
		return (MOD_PROP_PERM_WRITE);
	return (MOD_PROP_PERM_RW);
}

/*
 * Modifies the value of the property to default value or to the `pval'
 * specified by the user.
 */
/* ARGSUSED */
int
mod_set_boolean(netstack_t *stack, cred_t *cr, mod_prop_info_t *pinfo,
    const char *ifname, const void* pval, uint_t flags)
{
	char		*end;
	unsigned long	new_value;

	if (flags & MOD_PROP_DEFAULT) {
		pinfo->prop_cur_bval = pinfo->prop_def_bval;
		return (0);
	}

	if (ddi_strtoul(pval, &end, 10, &new_value) != 0 || *end != '\0')
		return (EINVAL);
	if (new_value != B_TRUE && new_value != B_FALSE)
		return (EINVAL);
	pinfo->prop_cur_bval = new_value;
	return (0);
}

/*
 * Retrieves property permission, default value, current value or possible
 * values for those properties whose value type is boolean_t.
 */
/* ARGSUSED */
int
mod_get_boolean(netstack_t *stack, mod_prop_info_t *pinfo, const char *ifname,
    void *pval, uint_t psize, uint_t flags)
{
	boolean_t	get_def = (flags & MOD_PROP_DEFAULT);
	boolean_t	get_perm = (flags & MOD_PROP_PERM);
	boolean_t	get_range = (flags & MOD_PROP_POSSIBLE);
	size_t		nbytes;

	bzero(pval, psize);
	if (get_perm)
		nbytes = snprintf(pval, psize, "%u", prop_perm2const(pinfo));
	else if (get_range)
		nbytes = snprintf(pval, psize, "%u,%u", B_FALSE, B_TRUE);
	else if (get_def)
		nbytes = snprintf(pval, psize, "%u", pinfo->prop_def_bval);
	else
		nbytes = snprintf(pval, psize, "%u", pinfo->prop_cur_bval);
	if (nbytes >= psize)
		return (ENOBUFS);
	return (0);
}

int
mod_uint32_value(const void *pval, mod_prop_info_t *pinfo, uint_t flags,
    ulong_t *new_value)
{
	char 		*end;

	if (flags & MOD_PROP_DEFAULT) {
		*new_value = pinfo->prop_def_uval;
		return (0);
	}

	if (ddi_strtoul(pval, &end, 10, (ulong_t *)new_value) != 0 ||
	    *end != '\0')
		return (EINVAL);
	if (*new_value < pinfo->prop_min_uval ||
	    *new_value > pinfo->prop_max_uval) {
		return (ERANGE);
	}
	return (0);
}

/*
 * Modifies the value of the property to default value or to the `pval'
 * specified by the user.
 */
/* ARGSUSED */
int
mod_set_uint32(netstack_t *stack, cred_t *cr, mod_prop_info_t *pinfo,
    const char *ifname, const void *pval, uint_t flags)
{
	unsigned long	new_value;
	int		err;

	if ((err = mod_uint32_value(pval, pinfo, flags, &new_value)) != 0)
		return (err);
	pinfo->prop_cur_uval = (uint32_t)new_value;
	return (0);
}

/*
 * Rounds up the value to make it multiple of 8.
 */
/* ARGSUSED */
int
mod_set_aligned(netstack_t *stack, cred_t *cr, mod_prop_info_t *pinfo,
    const char *ifname, const void* pval, uint_t flags)
{
	int	err;

	if ((err = mod_set_uint32(stack, cr, pinfo, ifname, pval, flags)) != 0)
		return (err);

	/* if required, align the value to multiple of 8 */
	if (pinfo->prop_cur_uval & 0x7) {
		pinfo->prop_cur_uval &= ~0x7;
		pinfo->prop_cur_uval += 0x8;
	}

	return (0);
}

/*
 * Retrieves property permission, default value, current value or possible
 * values for those properties whose value type is uint32_t.
 */
/* ARGSUSED */
int
mod_get_uint32(netstack_t *stack, mod_prop_info_t *pinfo, const char *ifname,
    void *pval, uint_t psize, uint_t flags)
{
	boolean_t	get_def = (flags & MOD_PROP_DEFAULT);
	boolean_t	get_perm = (flags & MOD_PROP_PERM);
	boolean_t	get_range = (flags & MOD_PROP_POSSIBLE);
	size_t		nbytes;

	bzero(pval, psize);
	if (get_perm)
		nbytes = snprintf(pval, psize, "%u", prop_perm2const(pinfo));
	else if (get_range)
		nbytes = snprintf(pval, psize, "%u-%u",
		    pinfo->prop_min_uval, pinfo->prop_max_uval);
	else if (get_def)
		nbytes = snprintf(pval, psize, "%u", pinfo->prop_def_uval);
	else
		nbytes = snprintf(pval, psize, "%u", pinfo->prop_cur_uval);
	if (nbytes >= psize)
		return (ENOBUFS);
	return (0);
}

/*
 * The range of the buffer size properties has a static lower bound configured
 * in the property info structure of the property itself, and a dynamic upper
 * bound.  The upper bound is the current value of the "max_buf" property
 * in the appropriate protocol property table.
 */
static void
mod_get_buf_prop_range(mod_prop_info_t ptbl[], mod_prop_info_t *pinfo,
    uint32_t *min, uint32_t *max)
{
	mod_prop_info_t *maxbuf_pinfo = mod_prop_lookup(ptbl, "max_buf",
	    pinfo->mpi_proto);

	*min = pinfo->prop_min_uval;
	*max = maxbuf_pinfo->prop_cur_uval;
}

/*
 * Modifies the value of the buffer size property to its default value or to
 * the value specified by the user.  This is similar to mod_set_uint32() except
 * that the value has a dynamically bounded range (see mod_get_buf_prop_range()
 * for details).
 */
/* ARGSUSED */
int
mod_set_buf_prop(mod_prop_info_t ptbl[], netstack_t *stack, cred_t *cr,
    mod_prop_info_t *pinfo, const char *ifname, const void *pval, uint_t flags)
{
	unsigned long	new_value;
	char		*end;
	uint32_t	min, max;

	if (flags & MOD_PROP_DEFAULT) {
		pinfo->prop_cur_uval = pinfo->prop_def_uval;
		return (0);
	}

	if (ddi_strtoul(pval, &end, 10, &new_value) != 0 || *end != '\0')
		return (EINVAL);

	mod_get_buf_prop_range(ptbl, pinfo, &min, &max);
	if (new_value < min || new_value > max)
		return (ERANGE);

	pinfo->prop_cur_uval = new_value;
	return (0);
}

/*
 * Retrieves property permissions, default value, current value, or possible
 * values for buffer size properties.  While these properties have integer
 * values, they have a dynamic range (see mod_get_buf_prop_range() for
 * details).  As such, they need to be handled differently.
 */
int
mod_get_buf_prop(mod_prop_info_t ptbl[], netstack_t *stack,
    mod_prop_info_t *pinfo, const char *ifname, void *pval, uint_t psize,
    uint_t flags)
{
	size_t nbytes;
	uint32_t min, max;

	if (flags & MOD_PROP_POSSIBLE) {
		mod_get_buf_prop_range(ptbl, pinfo, &min, &max);
		nbytes = snprintf(pval, psize, "%u-%u", min, max);
		return (nbytes < psize ? 0 : ENOBUFS);
	}
	return (mod_get_uint32(stack, pinfo, ifname, pval, psize, flags));
}

/*
 * Implements /sbin/ndd -get /dev/ip ?, for all the modules. Needed for
 * backward compatibility with /sbin/ndd.
 */
/* ARGSUSED */
int
mod_get_allprop(netstack_t *stack, mod_prop_info_t *pinfo, const char *ifname,
    void *val, uint_t psize, uint_t flags)
{
	char		*pval = val;
	mod_prop_info_t	*ptbl, *prop;
	uint_t		size;
	size_t		nbytes = 0, tbytes = 0;

	bzero(pval, psize);
	size = psize;

	switch (pinfo->mpi_proto) {
	case MOD_PROTO_IP:
	case MOD_PROTO_IPV4:
	case MOD_PROTO_IPV6:
		ptbl = stack->netstack_ip->ips_propinfo_tbl;
		break;
	case MOD_PROTO_RAWIP:
		ptbl = stack->netstack_icmp->is_propinfo_tbl;
		break;
	case MOD_PROTO_TCP:
		ptbl = stack->netstack_tcp->tcps_propinfo_tbl;
		break;
	case MOD_PROTO_UDP:
		ptbl = stack->netstack_udp->us_propinfo_tbl;
		break;
	case MOD_PROTO_SCTP:
		ptbl = stack->netstack_sctp->sctps_propinfo_tbl;
		break;
	default:
		return (EINVAL);
	}

	for (prop = ptbl; prop->mpi_name != NULL; prop++) {
		if (prop->mpi_name[0] == '\0' ||
		    strcmp(prop->mpi_name, "?") == 0) {
			continue;
		}
		nbytes = snprintf(pval, size, "%s %d %d", prop->mpi_name,
		    prop->mpi_proto, prop_perm2const(prop));
		size -= nbytes + 1;
		pval += nbytes + 1;
		tbytes += nbytes + 1;
		if (tbytes >= psize) {
			/* Buffer overflow, stop copying information */
			return (ENOBUFS);
		}
	}
	return (0);
}

/*
 * Hold a lock while changing *_epriv_ports to prevent multiple
 * threads from changing it at the same time.
 */
/* ARGSUSED */
int
mod_set_extra_privports(netstack_t *stack, cred_t *cr, mod_prop_info_t *pinfo,
    const char *ifname, const void* val, uint_t flags)
{
	uint_t		proto = pinfo->mpi_proto;
	tcp_stack_t	*tcps;
	sctp_stack_t	*sctps;
	udp_stack_t	*us;
	unsigned long	new_value;
	char		*end;
	kmutex_t	*lock;
	uint_t		i, nports;
	in_port_t	*ports;
	boolean_t	def = (flags & MOD_PROP_DEFAULT);
	const char	*pval = val;

	if (!def) {
		if (ddi_strtoul(pval, &end, 10, &new_value) != 0 ||
		    *end != '\0') {
			return (EINVAL);
		}

		if (new_value < pinfo->prop_min_uval ||
		    new_value > pinfo->prop_max_uval) {
			return (ERANGE);
		}
	}

	switch (proto) {
	case MOD_PROTO_TCP:
		tcps = stack->netstack_tcp;
		lock = &tcps->tcps_epriv_port_lock;
		ports = tcps->tcps_g_epriv_ports;
		nports = tcps->tcps_g_num_epriv_ports;
		break;
	case MOD_PROTO_UDP:
		us = stack->netstack_udp;
		lock = &us->us_epriv_port_lock;
		ports = us->us_epriv_ports;
		nports = us->us_num_epriv_ports;
		break;
	case MOD_PROTO_SCTP:
		sctps = stack->netstack_sctp;
		lock = &sctps->sctps_epriv_port_lock;
		ports = sctps->sctps_g_epriv_ports;
		nports = sctps->sctps_g_num_epriv_ports;
		break;
	default:
		return (ENOTSUP);
	}

	mutex_enter(lock);

	/* if MOD_PROP_DEFAULT is set then reset the ports list to default */
	if (def) {
		for (i = 0; i < nports; i++)
			ports[i] = 0;
		ports[0] = ULP_DEF_EPRIV_PORT1;
		ports[1] = ULP_DEF_EPRIV_PORT2;
		mutex_exit(lock);
		return (0);
	}

	/* Check if the value is already in the list */
	for (i = 0; i < nports; i++) {
		if (new_value == ports[i])
			break;
	}

	if (flags & MOD_PROP_REMOVE) {
		if (i == nports) {
			mutex_exit(lock);
			return (ESRCH);
		}
		/* Clear the value */
		ports[i] = 0;
	} else if (flags & MOD_PROP_APPEND) {
		if (i != nports) {
			mutex_exit(lock);
			return (EEXIST);
		}

		/* Find an empty slot */
		for (i = 0; i < nports; i++) {
			if (ports[i] == 0)
				break;
		}
		if (i == nports) {
			mutex_exit(lock);
			return (EOVERFLOW);
		}
		/* Set the new value */
		ports[i] = (in_port_t)new_value;
	} else {
		/*
		 * If the user used 'assignment' modifier.
		 * For eg:
		 * 	# ipadm set-prop -p extra_priv_ports=3001 tcp
		 *
		 * We clear all the ports and then just add 3001.
		 */
		ASSERT(flags == MOD_PROP_ACTIVE);
		for (i = 0; i < nports; i++)
			ports[i] = 0;
		ports[0] = (in_port_t)new_value;
	}

	mutex_exit(lock);
	return (0);
}

/*
 * Note: No locks are held when inspecting *_epriv_ports
 * but instead the code relies on:
 * - the fact that the address of the array and its size never changes
 * - the atomic assignment of the elements of the array
 */
/* ARGSUSED */
int
mod_get_extra_privports(netstack_t *stack, mod_prop_info_t *pinfo,
    const char *ifname, void *val, uint_t psize, uint_t flags)
{
	uint_t		proto = pinfo->mpi_proto;
	tcp_stack_t	*tcps;
	sctp_stack_t	*sctps;
	udp_stack_t	*us;
	uint_t		i, nports, size;
	in_port_t	*ports;
	char		*pval = val;
	size_t		nbytes = 0, tbytes = 0;
	boolean_t	get_def = (flags & MOD_PROP_DEFAULT);
	boolean_t	get_perm = (flags & MOD_PROP_PERM);
	boolean_t	get_range = (flags & MOD_PROP_POSSIBLE);

	bzero(pval, psize);
	size = psize;

	if (get_def) {
		tbytes = snprintf(pval, psize, "%u,%u", ULP_DEF_EPRIV_PORT1,
		    ULP_DEF_EPRIV_PORT2);
		goto ret;
	} else if (get_perm) {
		tbytes = snprintf(pval, psize, "%u", MOD_PROP_PERM_RW);
		goto ret;
	}

	switch (proto) {
	case MOD_PROTO_TCP:
		tcps = stack->netstack_tcp;
		ports = tcps->tcps_g_epriv_ports;
		nports = tcps->tcps_g_num_epriv_ports;
		break;
	case MOD_PROTO_UDP:
		us = stack->netstack_udp;
		ports = us->us_epriv_ports;
		nports = us->us_num_epriv_ports;
		break;
	case MOD_PROTO_SCTP:
		sctps = stack->netstack_sctp;
		ports = sctps->sctps_g_epriv_ports;
		nports = sctps->sctps_g_num_epriv_ports;
		break;
	default:
		return (ENOTSUP);
	}

	if (get_range) {
		tbytes = snprintf(pval, psize, "%u-%u", pinfo->prop_min_uval,
		    pinfo->prop_max_uval);
		goto ret;
	}

	for (i = 0; i < nports; i++) {
		if (ports[i] != 0) {
			if (psize == size)
				nbytes = snprintf(pval, size, "%u", ports[i]);
			else
				nbytes = snprintf(pval, size, ",%u", ports[i]);
			size -= nbytes;
			pval += nbytes;
			tbytes += nbytes;
			if (tbytes >= psize)
				return (ENOBUFS);
		}
	}
	return (0);
ret:
	if (tbytes >= psize)
		return (ENOBUFS);
	return (0);
}