root/usr/src/uts/common/inet/tunables.c
/*
 * 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);
}