/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include 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); }