/*
 * 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 conventions 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);
		SQUEUE_ENTER_ONE(connp->conn_sqp, mp, tcp_output, connp,
		    NULL, tcp_squeue_flag, 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), NULL);
		} else {
			putnext(connp->conn_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_ENTER_ONE(connp->conn_sqp, sqmp, tcp_kssl_input_asynch,
		    connp, NULL, SQ_FILL, SQTAG_TCP_KSSL_INPUT);
	} else {
		DTRACE_PROBE(kssl_err__allocb_failed);
	}
	CONN_DEC_REF(connp);
}
/*
 * tcp_input_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 connection 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 a different CPU,
 * which could decrement the conn/tcp reference before we get to increment it.
 */
void
tcp_kssl_input(tcp_t *tcp, mblk_t *mp, cred_t *cr)
{
	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;
	boolean_t	is_v4;
	void		*addr;

	if (is_system_labeled() && mp != NULL) {
		ASSERT(cr != NULL || msg_getcred(mp, NULL) != NULL);
		/*
		 * Provide for protocols above TCP such as RPC. NOPID leaves
		 * db_cpid unchanged.
		 * The cred could have already been set.
		 */
		if (cr != NULL)
			mblk_setcred(mp, cr, NOPID);
	}

	/* First time here, allocate the SSL context */
	if (tcp->tcp_kssl_ctx == NULL) {
		ASSERT(tcp->tcp_kssl_pending);

		is_v4 = (connp->conn_ipversion == IPV4_VERSION);
		if (is_v4) {
			addr = &connp->conn_faddr_v4;
		} else {
			addr = &connp->conn_faddr_v6;
		}

		if (kssl_init_context(tcp->tcp_kssl_ent,
		    addr, is_v4, 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:
			DTRACE_PROBE(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, NULL);

		/* FALLTHROUGH */
		case KSSL_CMD_NONE:
			DTRACE_PROBE(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_input_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_ENTER_ONE(
					    listener->tcp_connp->conn_sqp,
					    ind_mp, tcp_send_conn_ind,
					    listener->tcp_connp, NULL, SQ_FILL,
					    SQTAG_TCP_CONN_IND);
				}
			}
			break;

		case KSSL_CMD_QUEUED:
			DTRACE_PROBE(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:
			DTRACE_PROBE(kssl_cmd_proxy__ssl);
			/*
			 * Keep accumulating if not yet accepted.
			 */
			if (tcp->tcp_listener != NULL) {
				DTRACE_PROBE1(kssl_mblk__input_rcv_enqueue,
				    mblk_t *, outmp);
				tcp_rcv_enqueue(tcp, outmp, msgdsize(outmp),
				    NULL);
			} else {
				DTRACE_PROBE1(kssl_mblk__input_putnext,
				    mblk_t *, outmp);
				putnext(connp->conn_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:
			DTRACE_PROBE(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:
				DTRACE_PROBE1(kssl_no_can_do, tcp_t *, tcp);
				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_input_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_ENTER_ONE(
					    listener->tcp_connp->conn_sqp,
					    ind_mp, tcp_send_conn_ind,
					    listener->tcp_connp, NULL,
					    SQ_FILL, SQTAG_TCP_CONN_IND);
				}
			}
			if (mp != NULL)
				tcp_rcv_enqueue(tcp, mp, msgdsize(mp), NULL);
			break;
		}
		mp = NULL;
	} while (more);

	if (conn_held) {
		CONN_DEC_REF(connp);
	}
}
Beispiel #3
0
/*
 * Fusion output routine, called by tcp_output() and tcp_wput_proto().
 */
boolean_t
tcp_fuse_output(tcp_t *tcp, mblk_t *mp, uint32_t send_size)
{
	tcp_t *peer_tcp = tcp->tcp_loopback_peer;
	uint_t max_unread;
	boolean_t flow_stopped;
	boolean_t urgent = (DB_TYPE(mp) != M_DATA);

	ASSERT(tcp->tcp_fused);
	ASSERT(peer_tcp != NULL && peer_tcp->tcp_loopback_peer == tcp);
	ASSERT(tcp->tcp_connp->conn_sqp == peer_tcp->tcp_connp->conn_sqp);
	ASSERT(DB_TYPE(mp) == M_DATA || DB_TYPE(mp) == M_PROTO ||
	    DB_TYPE(mp) == M_PCPROTO);

	max_unread = peer_tcp->tcp_fuse_rcv_unread_hiwater;

	/* If this connection requires IP, unfuse and use regular path */
	if (TCP_LOOPBACK_IP(tcp) || TCP_LOOPBACK_IP(peer_tcp) ||
	    IPP_ENABLED(IPP_LOCAL_OUT|IPP_LOCAL_IN)) {
		TCP_STAT(tcp_fusion_aborted);
		tcp_unfuse(tcp);
		return (B_FALSE);
	}

	if (send_size == 0) {
		freemsg(mp);
		return (B_TRUE);
	}

	/*
	 * Handle urgent data; we either send up SIGURG to the peer now
	 * or do it later when we drain, in case the peer is detached
	 * or if we're short of memory for M_PCSIG mblk.
	 */
	if (urgent) {
		/*
		 * We stop synchronous streams when we have urgent data
		 * queued to prevent tcp_fuse_rrw() from pulling it.  If
		 * for some reasons the urgent data can't be delivered
		 * below, synchronous streams will remain stopped until
		 * someone drains the tcp_rcv_list.
		 */
		TCP_FUSE_SYNCSTR_PLUG_DRAIN(peer_tcp);
		tcp_fuse_output_urg(tcp, mp);
	}

	mutex_enter(&peer_tcp->tcp_fuse_lock);
	/*
	 * Wake up and signal the peer; it is okay to do this before
	 * enqueueing because we are holding the lock.  One of the
	 * advantages of synchronous streams is the ability for us to
	 * find out when the application performs a read on the socket,
	 * by way of tcp_fuse_rrw() entry point being called.  Every
	 * data that gets enqueued onto the receiver is treated as if
	 * it has arrived at the receiving endpoint, thus generating
	 * SIGPOLL/SIGIO for asynchronous socket just as in the strrput()
	 * case.  However, we only wake up the application when necessary,
	 * i.e. during the first enqueue.  When tcp_fuse_rrw() is called
	 * it will send everything upstream.
	 */
	if (peer_tcp->tcp_direct_sockfs && !urgent &&
	    !TCP_IS_DETACHED(peer_tcp)) {
		if (peer_tcp->tcp_rcv_list == NULL)
			STR_WAKEUP_SET(STREAM(peer_tcp->tcp_rq));
		/* Update poll events and send SIGPOLL/SIGIO if necessary */
		STR_SENDSIG(STREAM(peer_tcp->tcp_rq));
	}

	/*
	 * Enqueue data into the peer's receive list; we may or may not
	 * drain the contents depending on the conditions below.
	 */
	tcp_rcv_enqueue(peer_tcp, mp, send_size);

	/* In case it wrapped around and also to keep it constant */
	peer_tcp->tcp_rwnd += send_size;

	/*
	 * Exercise flow-control when needed; we will get back-enabled
	 * in either tcp_accept_finish(), tcp_unfuse(), or tcp_fuse_rrw().
	 * If tcp_direct_sockfs is on or if the peer endpoint is detached,
	 * we emulate streams flow control by checking the peer's queue
	 * size and high water mark; otherwise we simply use canputnext()
	 * to decide if we need to stop our flow.
	 *
	 * The outstanding unread data block check does not apply for a
	 * detached receiver; this is to avoid unnecessary blocking of the
	 * sender while the accept is currently in progress and is quite
	 * similar to the regular tcp.
	 */
	if (TCP_IS_DETACHED(peer_tcp) || max_unread == 0)
		max_unread = UINT_MAX;

	flow_stopped = tcp->tcp_flow_stopped;
	if (!flow_stopped &&
	    (((peer_tcp->tcp_direct_sockfs || TCP_IS_DETACHED(peer_tcp)) &&
	    (peer_tcp->tcp_rcv_cnt >= peer_tcp->tcp_fuse_rcv_hiwater ||
	    ++peer_tcp->tcp_fuse_rcv_unread_cnt >= max_unread)) ||
	    (!peer_tcp->tcp_direct_sockfs &&
	    !TCP_IS_DETACHED(peer_tcp) && !canputnext(peer_tcp->tcp_rq)))) {
		tcp_setqfull(tcp);
		flow_stopped = B_TRUE;
		TCP_STAT(tcp_fusion_flowctl);
		DTRACE_PROBE4(tcp__fuse__output__flowctl, tcp_t *, tcp,
		    uint_t, send_size, uint_t, peer_tcp->tcp_rcv_cnt,
		    uint_t, peer_tcp->tcp_fuse_rcv_unread_cnt);
	} else if (flow_stopped &&