root/usr/src/uts/common/io/mac/mac_ndd.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) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * functions to handle legacy ndd  ioctls
 */
#include <sys/types.h>
#include <sys/mac.h>
#include <sys/mac_impl.h>
#include <sys/mac_client_priv.h>
#include <inet/nd.h>
#include <sys/mac_ether.h>
#include <sys/policy.h>
#include <sys/strsun.h>

static int mac_ndd_set_ioctl(mac_impl_t *, mblk_t *, int, int *);
static int mac_ndd_get_ioctl(mac_impl_t *, mblk_t *, int, int *);
static int mac_ndd_get_names(mac_impl_t *, mblk_t *);
static boolean_t mac_add_name(mblk_t *, char *, int);

/*
 * add "<name> (<rwtag>) " into the mblk, allocating more memory if needed.
 */
static boolean_t
mac_add_name(mblk_t *mp, char *name, int ndd_flags)
{
        char *cp, *rwtag;
        int len, flags;

        flags = (ndd_flags & (MAC_PROP_PERM_WRITE|MAC_PROP_PERM_READ));
        switch (flags) {
        case 0:
                rwtag = "no read or write";
                break;
        case MAC_PROP_PERM_WRITE:
                rwtag = "write only";
                break;
        case MAC_PROP_PERM_READ:
                rwtag = "read only";
                break;
        default:
                rwtag = "read and write";
                break;
        }

        while (mp->b_cont != NULL)
                mp = mp->b_cont;
        /*
         * allocate space for name, <space>, '(', rwtag, ')', and
         * two terminating null chars.
         */
        len = strlen(name) + strlen(rwtag) + 6;
        if (mp->b_wptr + len >= mp->b_datap->db_lim) {
                mp->b_cont = allocb(len, BPRI_HI);
                mp = mp->b_cont;
                if (mp == NULL)
                        return (B_FALSE);
        }
        cp = (char *)mp->b_wptr;
        (void) snprintf(cp, len, "%s (%s)", name, rwtag);
        mp->b_wptr += strnlen(cp, len);
        mp->b_wptr++; /* skip past the terminating \0 */
        return (B_TRUE);
}


/*
 * handle a query for "ndd -get \?". The result is put into mp, and
 * more memory is allocated if needed. The resulting size of the data
 * is returned.
 */
static int
mac_ndd_get_names(mac_impl_t *mip, mblk_t *mp)
{
        int size_out, i;
        mblk_t *tmp;
        uint_t permflags;
        int status;
        uint64_t value;
        char *prop_name;

        if (!mac_add_name(mp, "?", MAC_PROP_PERM_READ))
                return (-1);

        /* first the known ndd mappings */
        for (i = 0; i < mip->mi_type->mt_mappingcount; i++) {
                if ((mip->mi_type->mt_mapping[i].mp_flags & MAC_PROP_MAP_KSTAT)
                    != 0)
                        permflags = MAC_PROP_PERM_READ;
                else {
                        status = mip->mi_callbacks->mc_getprop(mip->mi_driver,
                            mip->mi_type->mt_mapping[i].mp_name,
                            mip->mi_type->mt_mapping[i].mp_prop_id,
                            mip->mi_type->mt_mapping[i].mp_valsize, &value);
                        if (status != 0)
                                continue;
                        status = mac_prop_info((mac_handle_t)mip,
                            mip->mi_type->mt_mapping[i].mp_prop_id,
                            mip->mi_type->mt_mapping[i].mp_name, NULL, 0,
                            NULL, &permflags);
                        if (status != 0)
                                continue;
                }
                if (!mac_add_name(mp, mip->mi_type->mt_mapping[i].mp_name,
                    permflags))
                        return (-1);
        }

        /* now the driver's ndd variables */
        for (i = 0; i < mip->mi_priv_prop_count; i++) {

                prop_name = mip->mi_priv_prop[i];

                if (mac_prop_info((mac_handle_t)mip, MAC_PROP_PRIVATE,
                    prop_name, NULL, 0, NULL, &permflags) != 0)
                        return (-1);

                /* skip over the "_" */
                if (!mac_add_name(mp, &prop_name[1], permflags))
                        return (-1);
        }

        tmp = mp;
        while (tmp->b_cont != NULL)
                tmp = tmp->b_cont;
        *tmp->b_wptr++ = '\0';
        size_out = msgdsize(mp);
        return (size_out);
}


/*
 * Handle legacy ndd ioctls for ND_GET and ND_SET.
 */
void
mac_ndd_ioctl(mac_impl_t *mip, queue_t *wq, mblk_t *mp)
{
        IOCP    iocp;
        int     cmd, err, rval;

        iocp = (IOCP)mp->b_rptr;
        if (iocp->ioc_count == 0 || mp->b_cont == NULL) {
                err = EINVAL;
                goto done;
        }

        cmd = iocp->ioc_cmd;

        if (cmd == ND_SET) {
                err = mac_ndd_set_ioctl(mip, mp, iocp->ioc_count, &rval);
        } else if (cmd == ND_GET) {
                err = mac_ndd_get_ioctl(mip, mp, iocp->ioc_count, &rval);
        }
done:
        if (err == 0)
                miocack(wq, mp, msgdsize(mp->b_cont), rval);
        else
                miocnak(wq, mp, 0, err);
}

static int
mac_ndd_get_ioctl(mac_impl_t *mip, mblk_t *mp, int avail, int *rval)
{
        mblk_t          *mp1;
        char            *valp;
        uchar_t         *value;
        uint32_t        new_value;
        int             size_out = 0, i;
        int             status = EINVAL;
        char            *name, priv_name[MAXLINKPROPNAME];
        uint8_t         u8;
        uint16_t        u16;
        uint32_t        u32;
        uint64_t        u64;

        if (mp->b_cont == NULL || avail < 2)
                return (EINVAL);
        valp = (char *)mp->b_cont->b_rptr;
        mp1 = allocb(avail, BPRI_HI); /* the returned buffer */
        if (mp1 == NULL)
                return (ENOMEM);

        if (strcmp(valp, "?") == 0) {
                /*
                 * handle "ndd -get <..> \?" queries.
                 */
                size_out = mac_ndd_get_names(mip, mp1);
                if (size_out < 0) {
                        status = ENOMEM;
                        goto get_done;
                }
                if (size_out > avail) {
                        int excess;
                        char *cp;
                        /*
                         * need more user buffer space. Return as many
                         * mblks as will fit and return the needed
                         * buffer size in ioc_rval.
                         */
                        excess = size_out - avail;
                        *rval = size_out; /* what's needed */
                        size_out -= excess;
                        (void) adjmsg(mp1, -(excess + 1));
                        cp = (char *)mp1->b_wptr;
                        *cp = '\0';
                }
                status = 0;
                goto get_done;
        }

        ASSERT(mip->mi_callbacks->mc_callbacks & MC_GETPROP);
        name = valp;
        valp = (char *)mp1->b_rptr;
        mp1->b_wptr = mp1->b_rptr;

        /* first lookup ndd <-> public property mapping */
        for (i = 0; i < mip->mi_type->mt_mappingcount; i++) {
                if (strcmp(name, mip->mi_type->mt_mapping[i].mp_name) != 0)
                        continue;

                switch (mip->mi_type->mt_mapping[i].mp_valsize) {
                case 1:
                        value = (uchar_t *)&u8;
                        break;
                case 2:
                        value = (uchar_t *)&u16;
                        break;
                case 4:
                        value = (uchar_t *)&u32;
                        break;
                default:
                        value = (uchar_t *)&u64;
                        break;
                }

                if ((mip->mi_type->mt_mapping[i].mp_flags & MAC_PROP_MAP_KSTAT)
                    != 0) {
                        u64 = mac_stat_get((mac_handle_t)mip,
                            mip->mi_type->mt_mapping[i].mp_kstat);
                        status = 0;
                        /*
                         * ether_stats are all always KSTAT_DATA_UINT32
                         */
                        new_value = u32 = (long)u64;
                } else {
                        status = mip->mi_callbacks->mc_getprop(mip->mi_driver,
                            name, mip->mi_type->mt_mapping[i].mp_prop_id,
                            mip->mi_type->mt_mapping[i].mp_valsize, value);
                        switch (mip->mi_type->mt_mapping[i].mp_valsize) {
                        case 1:
                                new_value = u8;
                                break;
                        case 2:
                                new_value = u16;
                                break;
                        case 4:
                                new_value = u32;
                                break;
                        case 8:
                                /*
                                 * The only uint64_t is for speed, which is
                                 * converted to Mbps in ndd reports.
                                 */
                                new_value = (u64/1000000);
                                break;
                        }
                }

                if (status != 0)
                        goto get_done;

                (void) snprintf(valp, avail, "%d", new_value);
                goto update_reply;
        }

        /*
         * could not find a public property. try the private prop route
         * where all string processing will be done by the driver.
         */
        (void) snprintf(priv_name, sizeof (priv_name), "_%s", name);
        status = mip->mi_callbacks->mc_getprop(mip->mi_driver, priv_name,
            MAC_PROP_PRIVATE, avail - 2, mp1->b_rptr);
        if (status != 0)
                goto get_done;

update_reply:
        size_out += strnlen((const char *)mp1->b_rptr, avail);
        valp += size_out;
        *valp++ = '\0'; /* need \0\0 */
        *valp++ = '\0';
        mp1->b_wptr = (uchar_t *)valp;
        *rval = 0;

get_done:
        freemsg(mp->b_cont);
        if (status == 0)
                mp->b_cont = mp1;
        else {
                freemsg(mp1);
                mp->b_cont = NULL;
        }
        return (status);
}

static int
mac_ndd_set_ioctl(mac_impl_t *mip, mblk_t *mp, int avail, int *rval)
{
        mblk_t          *mp1;
        char            *valp, *name, *new_valuep;
        uchar_t         *vp;
        long            new_value;
        int             status, i;
        uint8_t         u8;
        uint16_t        u16;
        uint32_t        u32;
        IOCP            iocp;
        char            priv_name[MAXLINKPROPNAME];

        if (avail == 0 || !(mp1 = mp->b_cont))
                return (EINVAL);

        if (mp1->b_cont) {
                freemsg(mp1->b_cont);
                mp1->b_cont = NULL;
        }
        mp1->b_datap->db_lim[-1] = '\0';
        valp = (char *)mp1->b_rptr;
        name = valp;
        *rval = 0;
        while (*valp++)
                ;
        if (valp >= (char *)mp1->b_wptr)
                valp = NULL;

        new_valuep = valp;
        if (ddi_strtol(valp, NULL, 0, &new_value) != 0)
                goto priv_prop;

        iocp = (IOCP)mp->b_rptr;
        if (valp != NULL &&
            ((iocp->ioc_cr == NULL) ||
            ((status = secpolicy_net_config(iocp->ioc_cr, B_FALSE)) != 0)))
                return (status);

        status = EINVAL;

        /* first lookup ndd <-> public property mapping */
        for (i = 0; i < mip->mi_type->mt_mappingcount; i++) {
                if (strcmp(name, mip->mi_type->mt_mapping[i].mp_name) != 0)
                        continue;

                if (mip->mi_type->mt_mapping[i].mp_flags & MAC_PROP_MAP_KSTAT)
                        return (EINVAL);

                if (new_value > mip->mi_type->mt_mapping[i].mp_maxval ||
                    new_value < mip->mi_type->mt_mapping[i].mp_minval ||
                    (mip->mi_type->mt_mapping[i].mp_flags & MAC_PROP_PERM_WRITE)
                    == 0)
                        return (EINVAL);
                switch (mip->mi_type->mt_mapping[i].mp_valsize) {
                case 1:
                        u8 = (uint8_t)new_value;
                        vp = (uchar_t *)&u8;
                        break;
                case 2:
                        u16 = (uint16_t)new_value;
                        vp = (uchar_t *)&u16;
                        break;
                case 4:
                        u32 = (uint32_t)new_value;
                        vp = (uchar_t *)&u32;
                        break;
                case 8:
                        vp = (uchar_t *)&new_value;
                        break;
                default:
                        return (ENOTSUP);
                }

                status = mip->mi_callbacks->mc_setprop(mip->mi_driver,
                    name, mip->mi_type->mt_mapping[i].mp_prop_id,
                    mip->mi_type->mt_mapping[i].mp_valsize, (const void *)vp);
                goto done;
        }

priv_prop:
        (void) snprintf(priv_name, sizeof (priv_name), "_%s", name);
        status = mip->mi_callbacks->mc_setprop(mip->mi_driver, priv_name,
            MAC_PROP_PRIVATE, strlen(new_valuep), new_valuep);
done:
        freemsg(mp1);
        mp->b_cont = NULL;
        return (status);
}