/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void ip_helper_wput(queue_t *q, mblk_t *mp); static int ip_helper_stream_close(queue_t *, int); static struct module_info ip_helper_stream_info = { 0, "iphelper", IP_MOD_MINPSZ, IP_MOD_MAXPSZ, IP_MOD_HIWAT, IP_MOD_LOWAT }; static struct qinit ip_helper_stream_rinit = { NULL, NULL, NULL, ip_helper_stream_close, NULL, &ip_helper_stream_info, NULL }; static struct qinit ip_helper_stream_winit = { (pfi_t)ip_helper_wput, (pfi_t)ip_wsrv, NULL, NULL, NULL, &ip_helper_stream_info, NULL, NULL, NULL, STRUIOT_NONE }; #define IP_USE_HELPER_CACHE (ip_helper_stream_cache != NULL) /* * set the q_ptr of the 'q' to the conn_t pointer passed in */ static void ip_helper_share_conn(queue_t *q, mblk_t *mp, cred_t *crp) { /* * This operation is allowed only on helper streams with kcred */ if (kcred != crp || msgdsize(mp->b_cont) != sizeof (void *)) { miocnak(q, mp, 0, EINVAL); return; } if (IP_USE_HELPER_CACHE) { ip_helper_stream_info_t *ip_helper_info; ip_helper_info = *((ip_helper_stream_info_t **) mp->b_cont->b_rptr); ip_helper_info->iphs_minfo = q->q_ptr; ip_helper_info->iphs_rq = RD(q); ip_helper_info->iphs_wq = WR(q); } else { conn_t *connp = *((conn_t **)mp->b_cont->b_rptr); connp->conn_helper_info->iphs_minfo = q->q_ptr; connp->conn_helper_info->iphs_rq = RD(q); connp->conn_helper_info->iphs_wq = WR(q); WR(q)->q_ptr = RD(q)->q_ptr = (void *)connp; connp->conn_rq = RD(q); connp->conn_wq = WR(q); } miocack(q, mp, 0, 0); } void ip_helper_wput(queue_t *q, mblk_t *mp) { struct iocblk *iocp = (struct iocblk *)mp->b_rptr; if (DB_TYPE(mp) == M_IOCTL && iocp->ioc_cmd == SIOCSQPTR) { ip_helper_share_conn(q, mp, iocp->ioc_cr); } else { conn_t *connp = (conn_t *)q->q_ptr; if (connp->conn_af_isv6) { ip_wput_v6(q, mp); } else { ip_wput(q, mp); } } } /* ARGSUSED */ int ip_helper_stream_setup(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *credp, boolean_t isv6) { major_t maj; ip_helper_minfo_t *ip_minfop; ASSERT((flag & ~(FKLYR)) == IP_HELPER_STR); ASSERT(RD(q) == q); ip_minfop = kmem_alloc(sizeof (ip_helper_minfo_t), KM_NOSLEEP); if (ip_minfop == NULL) { return (ENOMEM); } ip_minfop->ip_minfo_dev = 0; ip_minfop->ip_minfo_arena = NULL; /* * Clone the device, allocate minor device number */ if (ip_minor_arena_la != NULL) ip_minfop->ip_minfo_dev = inet_minor_alloc(ip_minor_arena_la); if (ip_minfop->ip_minfo_dev == 0) { /* * numbers in the large arena are exhausted * Try small arena. * Or this is a 32 bit system, 32 bit systems do not have * ip_minor_arena_la */ ip_minfop->ip_minfo_dev = inet_minor_alloc(ip_minor_arena_sa); if (ip_minfop->ip_minfo_dev == 0) { return (EBUSY); } ip_minfop->ip_minfo_arena = ip_minor_arena_sa; } else { ip_minfop->ip_minfo_arena = ip_minor_arena_la; } ASSERT(ip_minfop->ip_minfo_dev != 0); ASSERT(ip_minfop->ip_minfo_arena != NULL); RD(q)->q_ptr = WR(q)->q_ptr = ip_minfop; maj = getemajor(*devp); *devp = makedevice(maj, (ulong_t)(ip_minfop->ip_minfo_dev)); q->q_qinfo = &ip_helper_stream_rinit; WR(q)->q_qinfo = &ip_helper_stream_winit; qprocson(q); return (0); } /* ARGSUSED */ static int ip_helper_stream_close(queue_t *q, int flag) { ip_helper_minfo_t *ip_minfop; qprocsoff(q); ip_minfop = (q)->q_ptr; inet_minor_free(ip_minfop->ip_minfo_arena, ip_minfop->ip_minfo_dev); kmem_free(ip_minfop, sizeof (ip_helper_minfo_t)); RD(q)->q_ptr = NULL; WR(q)->q_ptr = NULL; return (0); } /* * Public interface for creating an IP stream with shared conn_t */ /* ARGSUSED */ int ip_create_helper_stream(conn_t *connp, ldi_ident_t li) { int error; int ret; ASSERT(!servicing_interrupt()); error = 0; if (IP_USE_HELPER_CACHE) { connp->conn_helper_info = kmem_cache_alloc( ip_helper_stream_cache, KM_NOSLEEP); if (connp->conn_helper_info == NULL) return (EAGAIN); connp->conn_rq = connp->conn_helper_info->iphs_rq; connp->conn_wq = connp->conn_helper_info->iphs_wq; /* * Doesn't need to hold the QLOCK for there is no one else * should have a pointer to this queue. */ connp->conn_rq->q_flag |= QWANTR; connp->conn_wq->q_flag |= QWANTR; connp->conn_rq->q_ptr = connp; connp->conn_wq->q_ptr = connp; } else { ASSERT(connp->conn_helper_info == NULL); connp->conn_helper_info = kmem_alloc( sizeof (ip_helper_stream_info_t), KM_SLEEP); /* * open ip device via the layered interface. * pass in kcred as some threads do not have the * priviledge to open /dev/ip and the check in * secpolicy_spec_open() will fail the open */ error = ldi_open_by_name(connp->conn_af_isv6 ? DEV_IP6 : DEV_IP, IP_HELPER_STR, kcred, &connp->conn_helper_info->iphs_handle, li); if (error != 0) { kmem_free(connp->conn_helper_info, (sizeof (ip_helper_stream_info_t))); connp->conn_helper_info = NULL; return (error); } /* * Share connp with the helper stream */ error = ldi_ioctl(connp->conn_helper_info->iphs_handle, SIOCSQPTR, (intptr_t)connp, FKIOCTL, kcred, &ret); if (error != 0) { /* * Passing in a zero flag indicates that an error * occured and stream was not shared */ (void) ldi_close(connp->conn_helper_info->iphs_handle, 0, kcred); kmem_free(connp->conn_helper_info, (sizeof (ip_helper_stream_info_t))); connp->conn_helper_info = NULL; } } return (error); } /* * Public interface for freeing IP helper stream */ /* ARGSUSED */ void ip_free_helper_stream(conn_t *connp) { ASSERT(!servicing_interrupt()); if (IP_USE_HELPER_CACHE) { if (connp->conn_helper_info == NULL) return; ASSERT(connp->conn_helper_info->iphs_rq != NULL); ASSERT(connp->conn_helper_info->iphs_wq != NULL); /* Prevent service procedures from being called */ disable_svc(connp->conn_helper_info->iphs_rq); /* Wait until service procedure of each queue is run */ wait_svc(connp->conn_helper_info->iphs_rq); /* Cleanup any pending ioctls */ conn_ioctl_cleanup(connp); /* Allow service procedures to be called again */ enable_svc(connp->conn_helper_info->iphs_rq); /* Flush the queues */ flushq(connp->conn_helper_info->iphs_rq, FLUSHALL); flushq(connp->conn_helper_info->iphs_wq, FLUSHALL); connp->conn_helper_info->iphs_rq->q_ptr = NULL; connp->conn_helper_info->iphs_wq->q_ptr = NULL; kmem_cache_free(ip_helper_stream_cache, connp->conn_helper_info); } else { ASSERT( connp->conn_helper_info->iphs_handle != NULL); connp->conn_helper_info->iphs_rq->q_ptr = connp->conn_helper_info->iphs_wq->q_ptr = connp->conn_helper_info->iphs_minfo; (void) ldi_close(connp->conn_helper_info->iphs_handle, IP_HELPER_STR, kcred); kmem_free(connp->conn_helper_info, sizeof (ip_helper_stream_info_t)); } connp->conn_helper_info = NULL; } /* * create a T_SVR4_OPTMGMT_REQ TPI message and send down the IP stream */ static int ip_send_option_request(conn_t *connp, uint_t optset_context, int level, int option_name, const void *optval, t_uscalar_t optlen, cred_t *cr) { struct T_optmgmt_req *optmgmt_reqp; struct opthdr *ohp; ssize_t size; mblk_t *mp; size = sizeof (struct T_optmgmt_req) + sizeof (struct opthdr) + optlen; /* Not used to generate UCRED, thus don't need correct pid */ mp = allocb_cred(size, cr, NOPID); if (mp == NULL) return (ENOMEM); mp->b_datap->db_type = M_PROTO; optmgmt_reqp = (struct T_optmgmt_req *)mp->b_wptr; optmgmt_reqp->PRIM_type = T_SVR4_OPTMGMT_REQ; optmgmt_reqp->MGMT_flags = optset_context; optmgmt_reqp->OPT_length = (t_scalar_t)sizeof (struct opthdr) + optlen; optmgmt_reqp->OPT_offset = (t_scalar_t)sizeof (struct T_optmgmt_req); mp->b_wptr += sizeof (struct T_optmgmt_req); ohp = (struct opthdr *)mp->b_wptr; ohp->level = level; ohp->name = option_name; ohp->len = optlen; mp->b_wptr += sizeof (struct opthdr); if (optval != NULL) { bcopy(optval, mp->b_wptr, optlen); } else { bzero(mp->b_wptr, optlen); } mp->b_wptr += optlen; /* * Send down the primitive */ return (ldi_putmsg(connp->conn_helper_info->iphs_handle, mp)); } /* * wait/process the response to T_SVR4_OPTMGMT_REQ TPI message */ static int ip_get_option_response(conn_t *connp, uint_t optset_context, void *optval, t_uscalar_t *optlenp) { union T_primitives *tpr; int error; mblk_t *mp; mp = NULL; ASSERT(optset_context == T_CHECK || optset_context == T_NEGOTIATE); error = ldi_getmsg(connp->conn_helper_info->iphs_handle, &mp, NULL); if (error != 0) { return (error); } if (DB_TYPE(mp) != M_PCPROTO || MBLKL(mp) < sizeof (tpr->type)) { error = EPROTO; goto done; } tpr = (union T_primitives *)mp->b_rptr; switch (tpr->type) { case T_OPTMGMT_ACK: if (MBLKL(mp) < TOPTMGMTACKSZ) error = EPROTO; break; case T_ERROR_ACK: if (MBLKL(mp) < TERRORACKSZ) { error = EPROTO; break; } if (tpr->error_ack.TLI_error == TSYSERR) error = tpr->error_ack.UNIX_error; else error = proto_tlitosyserr(tpr->error_ack.TLI_error); break; default: error = EPROTO; break; } if ((optset_context == T_CHECK) && (error == 0)) { struct opthdr *opt_res; t_uscalar_t len; t_uscalar_t size; t_uscalar_t maxlen = *optlenp; void *option; struct T_optmgmt_ack *optmgmt_ack; optmgmt_ack = (struct T_optmgmt_ack *)mp->b_rptr; opt_res = (struct opthdr *) ((uintptr_t)mp->b_rptr + optmgmt_ack->OPT_offset); /* * Check mblk boundary */ if (!MBLKIN(mp, optmgmt_ack->OPT_offset, optmgmt_ack->OPT_length)) { error = EPROTO; goto done; } /* * Check alignment */ if ((((uintptr_t)opt_res) & (__TPI_ALIGN_SIZE - 1)) != 0) { error = EPROTO; goto done; } option = &opt_res[1]; /* check to ensure that the option is within bounds */ if ((((uintptr_t)option + opt_res->len) < (uintptr_t)option) || !MBLKIN(mp, sizeof (struct opthdr), opt_res->len)) { error = EPROTO; goto done; } len = opt_res->len; size = MIN(len, maxlen); /* * Copy data */ bcopy(option, optval, size); bcopy(&size, optlenp, sizeof (size)); } done: freemsg(mp); return (error); } /* * Public interface to get socketoptions via the ip helper stream. */ int ip_get_options(conn_t *connp, int level, int option_name, void *optval, t_uscalar_t *optlenp, cred_t *cr) { int error; error = ip_send_option_request(connp, T_CHECK, level, option_name, NULL, *optlenp, cr); if (error) return (error); return (ip_get_option_response(connp, T_CHECK, optval, optlenp)); } /* * Public interface to set socket options via the ip helper stream. */ int ip_set_options(conn_t *connp, int level, int option_name, const void *optval, t_uscalar_t optlen, cred_t *cr) { int error; error = ip_send_option_request(connp, T_NEGOTIATE, level, option_name, optval, optlen, cr); if (error) return (error); return (ip_get_option_response(connp, T_NEGOTIATE, (void *)optval, &optlen)); }