| /* |
| * 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 2007 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| #pragma ident "%Z%%M% %I% %E% SMI" |
| |
| #include <sys/types.h> |
| #include <sys/stream.h> |
| #include <sys/strsun.h> |
| #include <sys/strsubr.h> |
| #include <sys/stropts.h> |
| #include <sys/strlog.h> |
| #include <sys/strsun.h> |
| #include <sys/cmn_err.h> |
| #include <sys/debug.h> |
| #include <sys/vtrace.h> |
| #include <sys/kmem.h> |
| #include <sys/zone.h> |
| #include <sys/tihdr.h> |
| |
| #include <sys/errno.h> |
| #include <netinet/in.h> |
| #include <netinet/tcp.h> |
| |
| #include <inet/common.h> |
| #include <inet/ipclassifier.h> |
| #include <inet/ip.h> |
| #include <inet/ip6.h> |
| #include <inet/mi.h> |
| #include <inet/mib2.h> |
| #include <inet/tcp.h> |
| #include <inet/ipsec_impl.h> |
| #include <inet/ipdrop.h> |
| #include <inet/tcp_trace.h> |
| #include <inet/tcp_impl.h> |
| |
| #include <sys/squeue.h> |
| #include <inet/kssl/ksslapi.h> |
| |
| /* |
| * For the Kernel SSL proxy |
| * |
| * Routines in this file are called on tcp's incoming path, |
| * tcp_rput_data() mainly, and right before the message is |
| * to be putnext()'ed upstreams. |
| */ |
| |
| static void tcp_kssl_input_callback(void *, mblk_t *, kssl_cmd_t); |
| static void tcp_kssl_input_asynch(void *, mblk_t *, void *); |
| |
| extern void tcp_output(void *, mblk_t *, void *); |
| extern void tcp_send_conn_ind(void *, mblk_t *, void *); |
| |
| extern squeue_func_t tcp_squeue_wput_proc; |
| |
| /* |
| * tcp_rput_data() calls this routine for all packet destined to a |
| * connection to the SSL port, when the SSL kernel proxy is configured |
| * to intercept and process those packets. |
| * A packet may carry multiple SSL records, so the function |
| * calls kssl_input() in a loop, until all records are |
| * handled. |
| * As long as this conection is in handshake, that is until the first |
| * time kssl_input() returns a record to be delivered ustreams, |
| * we maintain the tcp_kssl_inhandshake, and keep an extra reference on |
| * the tcp/connp across the call to kssl_input(). The reason is, that |
| * function may return KSSL_CMD_QUEUED after scheduling an asynchronous |
| * request and cause tcp_kssl_callback() to be called on adifferent CPU, |
| * which could decrement the conn/tcp reference before we get to increment it. |
| */ |
| void |
| tcp_kssl_input(tcp_t *tcp, mblk_t *mp) |
| { |
| struct conn_s *connp = tcp->tcp_connp; |
| tcp_t *listener; |
| mblk_t *ind_mp; |
| kssl_cmd_t kssl_cmd; |
| mblk_t *outmp; |
| struct T_conn_ind *tci; |
| boolean_t more = B_FALSE; |
| boolean_t conn_held = B_FALSE; |
| |
| /* First time here, allocate the SSL context */ |
| if (tcp->tcp_kssl_ctx == NULL) { |
| ASSERT(tcp->tcp_kssl_pending); |
| |
| if (kssl_init_context(tcp->tcp_kssl_ent, |
| tcp->tcp_ipha->ipha_dst, tcp->tcp_mss, |
| &(tcp->tcp_kssl_ctx)) != KSSL_STS_OK) { |
| tcp->tcp_kssl_pending = B_FALSE; |
| kssl_release_ent(tcp->tcp_kssl_ent, NULL, |
| KSSL_NO_PROXY); |
| tcp->tcp_kssl_ent = NULL; |
| goto no_can_do; |
| } |
| tcp->tcp_kssl_inhandshake = B_TRUE; |
| |
| /* we won't be needing this one after now */ |
| kssl_release_ent(tcp->tcp_kssl_ent, NULL, KSSL_NO_PROXY); |
| tcp->tcp_kssl_ent = NULL; |
| |
| } |
| |
| if (tcp->tcp_kssl_inhandshake) { |
| CONN_INC_REF(connp); |
| conn_held = B_TRUE; |
| } |
| do { |
| kssl_cmd = kssl_input(tcp->tcp_kssl_ctx, mp, &outmp, |
| &more, tcp_kssl_input_callback, (void *)tcp); |
| |
| switch (kssl_cmd) { |
| case KSSL_CMD_SEND: |
| /* |
| * We need to increment tcp_squeue_bytes to account |
| * for the extra bytes internally injected to the |
| * outgoing flow. tcp_output() will decrement it |
| * as they are sent out. |
| */ |
| mutex_enter(&tcp->tcp_non_sq_lock); |
| tcp->tcp_squeue_bytes += msgdsize(outmp); |
| mutex_exit(&tcp->tcp_non_sq_lock); |
| tcp_output(connp, outmp, NULL); |
| |
| /* FALLTHROUGH */ |
| case KSSL_CMD_NONE: |
| if (tcp->tcp_kssl_pending) { |
| mblk_t *ctxmp; |
| |
| /* |
| * SSL handshake successfully started - |
| * pass up the T_CONN_IND |
| */ |
| |
| mp = NULL; |
| |
| listener = tcp->tcp_listener; |
| tcp->tcp_kssl_pending = B_FALSE; |
| |
| ind_mp = tcp->tcp_conn.tcp_eager_conn_ind; |
| ASSERT(ind_mp != NULL); |
| |
| ctxmp = allocb(sizeof (kssl_ctx_t), BPRI_MED); |
| |
| /* |
| * Give this session a chance to fall back to |
| * userland SSL |
| */ |
| if (ctxmp == NULL) |
| goto no_can_do; |
| |
| /* |
| * attach the kssl_ctx to the conn_ind and |
| * transform it to a T_SSL_PROXY_CONN_IND. |
| * Hold it so that it stays valid till it |
| * reaches the stream head. |
| */ |
| kssl_hold_ctx(tcp->tcp_kssl_ctx); |
| *((kssl_ctx_t *)ctxmp->b_rptr) = |
| tcp->tcp_kssl_ctx; |
| ctxmp->b_wptr = ctxmp->b_rptr + |
| sizeof (kssl_ctx_t); |
| |
| ind_mp->b_cont = ctxmp; |
| |
| tci = (struct T_conn_ind *)ind_mp->b_rptr; |
| tci->PRIM_type = T_SSL_PROXY_CONN_IND; |
| |
| /* |
| * The code below is copied from tcp_rput_data() |
| * delivering the T_CONN_IND on a TCPS_SYN_RCVD, |
| * and all conn ref cnt comments apply. |
| */ |
| tcp->tcp_conn.tcp_eager_conn_ind = NULL; |
| tcp->tcp_tconnind_started = B_TRUE; |
| |
| CONN_INC_REF(connp); |
| |
| CONN_INC_REF(listener->tcp_connp); |
| if (listener->tcp_connp->conn_sqp == |
| connp->conn_sqp) { |
| tcp_send_conn_ind(listener->tcp_connp, |
| ind_mp, |
| listener->tcp_connp->conn_sqp); |
| CONN_DEC_REF(listener->tcp_connp); |
| } else { |
| squeue_fill( |
| listener->tcp_connp->conn_sqp, |
| ind_mp, tcp_send_conn_ind, |
| listener->tcp_connp, |
| SQTAG_TCP_CONN_IND); |
| } |
| } |
| break; |
| |
| case KSSL_CMD_QUEUED: |
| /* |
| * We hold the conn_t here because an asynchronous |
| * request have been queued and |
| * tcp_kssl_input_callback() will be called later. |
| * It will release the conn_t |
| */ |
| CONN_INC_REF(connp); |
| break; |
| |
| case KSSL_CMD_DELIVER_PROXY: |
| case KSSL_CMD_DELIVER_SSL: |
| /* |
| * Keep accumulating if not yet accepted. |
| */ |
| if (tcp->tcp_listener != NULL) { |
| tcp_rcv_enqueue(tcp, outmp, msgdsize(outmp)); |
| } else { |
| putnext(tcp->tcp_rq, outmp); |
| } |
| /* |
| * We're at a phase where records are sent upstreams, |
| * past the handshake |
| */ |
| tcp->tcp_kssl_inhandshake = B_FALSE; |
| break; |
| |
| case KSSL_CMD_NOT_SUPPORTED: |
| /* |
| * Stop the SSL processing by the proxy, and |
| * switch to the userland SSL |
| */ |
| if (tcp->tcp_kssl_pending) { |
| |
| tcp->tcp_kssl_pending = B_FALSE; |
| |
| no_can_do: |
| listener = tcp->tcp_listener; |
| ind_mp = tcp->tcp_conn.tcp_eager_conn_ind; |
| ASSERT(ind_mp != NULL); |
| |
| if (tcp->tcp_kssl_ctx != NULL) { |
| kssl_release_ctx(tcp->tcp_kssl_ctx); |
| tcp->tcp_kssl_ctx = NULL; |
| } |
| |
| /* |
| * Make this a T_SSL_PROXY_CONN_IND, for the |
| * stream head to deliver it to the SSL |
| * fall-back listener |
| */ |
| tci = (struct T_conn_ind *)ind_mp->b_rptr; |
| tci->PRIM_type = T_SSL_PROXY_CONN_IND; |
| |
| /* |
| * The code below is copied from tcp_rput_data() |
| * delivering the T_CONN_IND on a TCPS_SYN_RCVD, |
| * and all conn ref cnt comments apply. |
| */ |
| tcp->tcp_conn.tcp_eager_conn_ind = NULL; |
| tcp->tcp_tconnind_started = B_TRUE; |
| |
| CONN_INC_REF(connp); |
| |
| CONN_INC_REF(listener->tcp_connp); |
| if (listener->tcp_connp->conn_sqp == |
| connp->conn_sqp) { |
| tcp_send_conn_ind(listener->tcp_connp, |
| ind_mp, |
| listener->tcp_connp->conn_sqp); |
| CONN_DEC_REF(listener->tcp_connp); |
| } else { |
| squeue_fill( |
| listener->tcp_connp->conn_sqp, |
| ind_mp, tcp_send_conn_ind, |
| listener->tcp_connp, |
| SQTAG_TCP_CONN_IND); |
| } |
| } |
| if (mp != NULL) |
| tcp_rcv_enqueue(tcp, mp, msgdsize(mp)); |
| break; |
| } |
| mp = NULL; |
| } while (more); |
| if (conn_held) { |
| CONN_DEC_REF(connp); |
| } |
| } |
| |
| /* |
| * Callback function for the cases kssl_input() had to submit an asynchronous |
| * job and need to come back when done to carry on the input processing. |
| * This routine follows the conentions of timeout and interrupt handlers. |
| * (no blocking, ...) |
| */ |
| static void |
| tcp_kssl_input_callback(void *arg, mblk_t *mp, kssl_cmd_t kssl_cmd) |
| { |
| tcp_t *tcp = (tcp_t *)arg; |
| conn_t *connp; |
| mblk_t *sqmp; |
| |
| ASSERT(tcp != NULL); |
| |
| connp = tcp->tcp_connp; |
| |
| ASSERT(connp != NULL); |
| |
| switch (kssl_cmd) { |
| case KSSL_CMD_SEND: |
| /* I'm coming from an outside perimeter */ |
| if (mp != NULL) { |
| /* |
| * See comment in tcp_kssl_input() call to tcp_output() |
| */ |
| mutex_enter(&tcp->tcp_non_sq_lock); |
| tcp->tcp_squeue_bytes += msgdsize(mp); |
| mutex_exit(&tcp->tcp_non_sq_lock); |
| } |
| CONN_INC_REF(connp); |
| (*tcp_squeue_wput_proc)(connp->conn_sqp, mp, |
| tcp_output, connp, SQTAG_TCP_OUTPUT); |
| |
| /* FALLTHROUGH */ |
| case KSSL_CMD_NONE: |
| break; |
| |
| case KSSL_CMD_DELIVER_PROXY: |
| case KSSL_CMD_DELIVER_SSL: |
| /* |
| * Keep accumulating if not yet accepted. |
| */ |
| if (tcp->tcp_listener != NULL) { |
| tcp_rcv_enqueue(tcp, mp, msgdsize(mp)); |
| } else { |
| putnext(tcp->tcp_rq, mp); |
| } |
| break; |
| |
| case KSSL_CMD_NOT_SUPPORTED: |
| /* Stop the SSL processing */ |
| kssl_release_ctx(tcp->tcp_kssl_ctx); |
| tcp->tcp_kssl_ctx = NULL; |
| } |
| /* |
| * Process any input that may have accumulated while we're waiting for |
| * the call-back. |
| * We need to re-enter the squeue for this connp, and a new mp is |
| * necessary. |
| */ |
| if ((sqmp = allocb(1, BPRI_MED)) != NULL) { |
| CONN_INC_REF(connp); |
| squeue_fill(connp->conn_sqp, sqmp, tcp_kssl_input_asynch, |
| connp, SQTAG_TCP_KSSL_INPUT); |
| } |
| #ifdef DEBUG |
| else { |
| cmn_err(CE_WARN, "tcp_kssl_input_callback: alloc failure"); |
| } |
| #endif /* DEBUG */ |
| CONN_DEC_REF(connp); |
| } |
| |
| /* |
| * Needed by tcp_kssl_input_callback() to continue processing the incoming |
| * flow on a tcp_t after an asynchronous callback call. |
| */ |
| /* ARGSUSED */ |
| void |
| tcp_kssl_input_asynch(void *arg, mblk_t *mp, void *arg2) |
| { |
| conn_t *connp = (conn_t *)arg; |
| tcp_t *tcp = connp->conn_tcp; |
| |
| ASSERT(connp != NULL); |
| freemsg(mp); |
| |
| /* |
| * NULL tcp_kssl_ctx means this connection is getting/was closed |
| * while we're away |
| */ |
| if (tcp->tcp_kssl_ctx != NULL) { |
| tcp_kssl_input(tcp, NULL); |
| } |
| } |