/*-
* Copyright (c) 2004,2005,2006,2011 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Wasabi Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* find_handler:
* Search the event handler list for the given ID.
*
* Parameter:
* id The handler ID.
*
* Returns:
* Pointer to handler if found, else NULL.
*/
/*
* check_event:
* Return first queued event. Optionally wait for arrival of event.
*
* Parameter:
* par The parameter.
* wait Wait for event if true
*/
/*
* add_event
* Adds an event entry to each registered handler queue.
* Note that events are simply duplicated because we expect the number of
* handlers to be very small, usually 1 (the daemon).
*
* Parameters:
* kind The event kind
* sid The ID of the affected session
* cid The ID of the affected connection
* reason The reason code
*/
/*
* check_event_handlers
* Checks for dead event handlers. A dead event handler would deplete
* memory over time, so we have to make sure someone at the other
* end is actively monitoring events.
* This function is called every 30 seconds or so (less frequent if there
* is other activity for the cleanup thread to deal with) to go through
* the list of handlers and check whether the first element in the event
* list has changed at all. If not, the event is deregistered.
* Note that this will not detect dead handlers if no events are pending,
* but we don't care as long as events don't accumulate in the list.
*
*/
/*
* get_socket:
* Get the file pointer from the socket handle passed into login.
*
* Parameter:
* fdes IN: The socket handle
* fpp OUT: The pointer to the resulting file pointer
*
* Returns: 0 on success, else an error code.
*
*/
if ((fp = fd_getfile(fdes)) == NULL) {
return EBADF;
}
if (fp->f_type != DTYPE_SOCKET) {
return ENOTSOCK;
}
/* Add the reference */
mutex_enter(&fp->f_lock);
fp->f_count++;
mutex_exit(&fp->f_lock);
*fpp = fp;
return 0;
}
/*
* release_socket:
* Release the file pointer from the socket handle passed into login.
*
* Parameter:
* fp IN: The pointer to the resulting file pointer
*
*/
/*
* find_connection:
* Find a connection by ID.
*
* Parameter: the session pointer and the connection ID
*
* Returns: The pointer to the connection (or NULL if not found)
*/
/*
* ref_session:
* Reference a session
*
* Session cannot be release until reference count reaches zero
*
* Returns: 1 if reference counter would overflow
*/
/*
* kill_connection:
* Terminate the connection as gracefully as possible.
*
* Parameter:
* conn The connection to terminate
* status The status code for the connection's "terminating" field
* logout The logout reason code
* recover Attempt to recover connection
*/
void
kill_connection(connection_t *conn, uint32_t status, int logout, bool recover)
{
session_t *sess = conn->c_session;
int terminating;
if (conn->c_state == ST_FULL_FEATURE) {
sess->s_active_connections--;
conn->c_state = ST_WINDING_DOWN;
/* If this is the last connection and ERL < 2, reset TSIH */
if (!sess->s_active_connections && sess->s_ErrorRecoveryLevel < 2)
sess->s_TSIH = 0;
/* Don't try to log out if the socket is broken or we're in the middle */
/* of logging in */
if (logout >= 0) {
if (sess->s_ErrorRecoveryLevel < 2 &&
logout == RECOVER_CONNECTION) {
logout = LOGOUT_CONNECTION;
}
if (!sess->s_active_connections &&
logout == LOGOUT_CONNECTION) {
logout = LOGOUT_SESSION;
}
/* connection is terminating, prevent cleanup */
mutex_enter(&conn->c_lock);
conn->c_usecount++;
mutex_exit(&conn->c_lock);
mutex_exit(&iscsi_cleanup_mtx);
DEBC(conn, 1, ("Send_logout for reason %d\n", logout));
if (!send_logout(conn, conn, logout, FALSE)) {
mutex_enter(&conn->c_lock);
conn->c_usecount--;
conn->c_terminating = ISCSI_STATUS_SUCCESS;
mutex_exit(&conn->c_lock);
return;
}
/*
* if the logout request was successfully sent,
* the logout response handler will do the rest
* of the termination processing. If the logout
* doesn't get a response, we'll get back in here
* once the timeout hits.
*/
done:
/* let send thread take over next step of cleanup */
mutex_enter(&conn->c_lock);
cv_broadcast(&conn->c_conn_cv);
mutex_exit(&conn->c_lock);
DEBC(conn, 5, ("kill_connection returns\n"));
}
/*
* kill_session:
* Terminate the session as gracefully as possible.
*
* Parameter:
* session Session to terminate
* status The status code for the termination
* logout The logout reason code
if (sess->s_terminating) {
mutex_exit(&iscsi_cleanup_mtx);
DEB(5, ("Session is being killed with status %d\n",sess->s_terminating));
return;
}
/*
* don't do anything if session isn't established yet, termination will be
* handled elsewhere
*/
if (sess->s_sessions.tqe_next == NULL && sess->s_sessions.tqe_prev == NULL) {
mutex_exit(&iscsi_cleanup_mtx);
DEB(5, ("Session is being killed which is not yet established\n"));
return;
}
if (recover) {
/*
* Only recover when there's just one active connection left.
* Otherwise we get in all sorts of timing problems, and it doesn't
* make much sense anyway to recover when the other side has
* requested that we kill a multipathed session.
*/
conn = NULL;
if (sess->s_active_connections == 1)
conn = assign_connection(sess, FALSE);
mutex_exit(&iscsi_cleanup_mtx);
if (conn != NULL)
kill_connection(conn, status, logout, TRUE);
return;
}
if (sess->s_refcount > 0) {
mutex_exit(&iscsi_cleanup_mtx);
DEB(5, ("Session is being killed while in use (refcnt = %d)\n",
sess->s_refcount));
return;
}
/* Remove session from global list */
sess->s_terminating = status;
TAILQ_REMOVE(&iscsi_sessions, sess, s_sessions);
sess->s_sessions.tqe_next = NULL;
sess->s_sessions.tqe_prev = NULL;
/*
* If all connections are already gone, trigger cleanup
* otherwise, the last connection will do this
*/
if (sess->s_active_connections == 0)
iscsi_notify_cleanup();
/*
* create_connection:
* Create and init the necessary framework for a connection:
* Take over userland socket
* Alloc the connection structure itself
* Copy connection parameters
* Create the send and receive threads
* And finally, log in.
*
* Parameter:
* par IN/OUT: The login parameters
* session IN: The owning session
* l IN: The lwp pointer of the caller
*
* Returns: 0 on success
* >0 on failure, connection structure deleted
* <0 on failure, connection is still terminating
*/
static int
create_connection(iscsi_login_parameters_t *par, session_t *sess,
struct lwp *l)
{
connection_t *conn;
int rc;
DEB(1, ("Create Connection for Session %d\n", sess->s_id));
if (sess->s_MaxConnections &&
sess->s_active_connections >= sess->s_MaxConnections) {
DEBOUT(("Too many connections (max = %d, curr = %d)\n",
sess->s_MaxConnections, sess->s_active_connections));
/* Always close descriptor */
fd_close(par->socket);
/*
* We must close the socket here to force the receive
* thread to wake up
*/
DEBC(conn, 1, ("Closing Socket %p\n", conn->c_sock));
mutex_enter(&conn->c_sock->f_lock);
conn->c_sock->f_count += 1;
mutex_exit(&conn->c_sock->f_lock);
closef(conn->c_sock);
/* give receive thread time to exit */
kpause("settle", false, 2 * hz, NULL);
/*
* At this point, each thread will tie 'sock' into its own file descriptor
* tables w/o increasing the use count - they will inherit the use
* increments performed in get_socket().
*/
if ((rc = send_login(conn)) != 0) {
DEBC(conn, 0, ("Login failed (rc %d)\n", rc));
/* Don't attempt to recover, there seems to be something amiss */
kill_connection(conn, rc, NO_LOGOUT, FALSE);
par->status = rc;
return -1;
}
DEBC(conn, 5, ("Connection created successfully!\n"));
return 0;
}
/*
* recreate_connection:
* Revive dead connection
*
* Parameter:
* par IN/OUT: The login parameters
* conn IN: The connection
* l IN: The lwp pointer of the caller
*
* Returns: 0 on success
* >0 on failure, connection structure deleted
* <0 on failure, connection is still terminating
*/
rc = send_task_management(conn, ccb, NULL, TASK_REASSIGN);
/* if we get an error on reassign, restart the original request */
if (rc && ccb->ccb_pdu_waiting != NULL) {
mutex_enter(&sess->s_lock);
if (sn_a_lt_b(ccb->ccb_CmdSN, sess->s_ExpCmdSN)) {
pdu = ccb->ccb_pdu_waiting;
sn = get_sernum(sess, pdu);
/*
* check_login_pars:
* Check the parameters passed into login/add_connection
* for validity and consistency.
*
* Parameter:
* par The login parameters
*
* Returns: 0 on success, else an error code.
*/
static int
check_login_pars(iscsi_login_parameters_t *par)
{
int i, n;
if (par->is_present.auth_info) {
/* check consistency of authentication parameters */
if (par->auth_info.auth_number > ISCSI_AUTH_OPTIONS) {
DEBOUT(("Auth number invalid: %d\n", par->auth_info.auth_number));
return ISCSI_STATUS_PARAMETER_INVALID;
}
if (par->auth_info.auth_number > 2) {
DEBOUT(("Auth number invalid: %d\n", par->auth_info.auth_number));
return ISCSI_STATUS_NOTIMPL;
}
for (i = 0, n = 0; i < par->auth_info.auth_number; i++) {
#if 0
if (par->auth_info.auth_type[i] < ISCSI_AUTH_None) {
DEBOUT(("Auth type invalid: %d\n",
par->auth_info.auth_type[i]));
return ISCSI_STATUS_PARAMETER_INVALID;
}
#endif
if (par->auth_info.auth_type[i] > ISCSI_AUTH_CHAP) {
DEBOUT(("Auth type invalid: %d\n",
par->auth_info.auth_type[i]));
return ISCSI_STATUS_NOTIMPL;
}
n = max(n, par->auth_info.auth_type[i]);
}
if (n) {
if (!par->is_present.password ||
(par->auth_info.mutual_auth &&
!par->is_present.target_password)) {
DEBOUT(("Password missing\n"));
return ISCSI_STATUS_PARAMETER_MISSING;
}
/* Note: Default for user-name is initiator name */
}
}
if (par->login_type != ISCSI_LOGINTYPE_DISCOVERY &&
!par->is_present.TargetName) {
DEBOUT(("Target name missing, login type %d\n", par->login_type));
return ISCSI_STATUS_PARAMETER_MISSING;
}
if (par->is_present.MaxRecvDataSegmentLength) {
if (par->MaxRecvDataSegmentLength < 512 ||
par->MaxRecvDataSegmentLength > 0xffffff) {
DEBOUT(("MaxRecvDataSegmentLength invalid: %d\n",
par->MaxRecvDataSegmentLength));
return ISCSI_STATUS_PARAMETER_INVALID;
}
}
return 0;
}
/*
* login:
* Handle the login ioctl - Create a session:
* Alloc the session structure
* Copy session parameters
* And call create_connection to establish the connection.
*
* Parameter:
* par IN/OUT: The login parameters
* l IN: The lwp pointer of the caller
*/
DEB(5, ("ISCSI: remove_connection %d from session %d\n",
par->connection_id, par->session_id));
mutex_enter(&iscsi_cleanup_mtx);
if ((session = find_session(par->session_id)) == NULL) {
mutex_exit(&iscsi_cleanup_mtx);
DEBOUT(("Session %d not found\n", par->session_id));
par->status = ISCSI_STATUS_INVALID_SESSION_ID;
return;
}
if ((conn = find_connection(session, par->connection_id)) == NULL) {
mutex_exit(&iscsi_cleanup_mtx);
DEBOUT(("Connection %d not found in session %d\n",
par->connection_id, par->session_id));
/*
* restore_connection:
* Handle the restore_connection ioctl.
*
* Parameter:
* par IN/OUT: The login parameters
* l IN: The lwp pointer of the caller
*/
/*
* send_targets:
* Handle the send_targets ioctl.
* Note: If the passed buffer is too small to hold the complete response,
* the response is kept in the session structure so it can be
* retrieved with the next call to this function without having to go to
* the target again. Once the complete response has been retrieved, it
* is discarded.
*
* Parameter:
* par IN/OUT: The send_targets parameters
*/
/* If all of the response was copied, don't keep it around */
if (rlen && par->response_used == rlen) {
free(sess->s_target_list, M_TEMP);
sess->s_target_list = NULL;
}
par->status = ISCSI_STATUS_SUCCESS;
}
/*
* set_node_name:
* Handle the set_node_name ioctl.
*
* Parameter:
* par IN/OUT: The set_node_name parameters
*/
/*
* kill_all_sessions:
* Terminate all sessions (called when the driver unloads).
*/
int
kill_all_sessions(void)
{
session_t *sess;
int rc = 0;
uint32_t sid;
mutex_enter(&iscsi_cleanup_mtx);
while ((sess = TAILQ_FIRST(&iscsi_sessions)) != NULL) {
sid = sess->s_id;
mutex_exit(&iscsi_cleanup_mtx);
kill_session(sid, ISCSI_STATUS_DRIVER_UNLOAD, LOGOUT_SESSION,
FALSE);
mutex_enter(&iscsi_cleanup_mtx);
}
if (TAILQ_FIRST(&iscsi_sessions) != NULL) {
DEBOUT(("Failed to kill all sessions\n"));
rc = EBUSY;
}
mutex_exit(&iscsi_cleanup_mtx);
return rc;
}
/*
* handle_connection_error:
* Deal with a problem during send or receive.
*
* Parameter:
* conn The connection the problem is associated with
* status The status code to insert into any unfinished CCBs
* dologout Whether Logout should be attempted
*/
if (!conn->c_terminating && conn->c_state <= ST_LOGOUT_SENT) {
/* if we get an error while winding down, escalate it */
if (dologout >= 0 && conn->c_state >= ST_WINDING_DOWN) {
dologout = NO_LOGOUT;
}
kill_connection(conn, status, dologout, TRUE);
}
}
/*
* remove a connection from session and add to the cleanup list
*/
void
add_connection_cleanup(connection_t *conn)
{
session_t *sess = NULL;
/*
* This implies that connection cleanup only runs when
* the send/recv threads have been killed
*/
DEBC(conn, 5, ("Cleanup: Waiting for threads to exit\n"));
mutex_enter(&conn->c_lock);
while (conn->c_sendproc || conn->c_rcvproc)
kpause("threads", false, hz, &conn->c_lock);
for (s=1; conn->c_usecount > 0 && s < 3; ++s)
kpause("usecount", false, hz, &conn->c_lock);
/* Go to sleep, but wake up every 120 seconds to
* check for dead event handlers */
rc = cv_timedwait(&iscsi_cleanup_cv, &iscsi_cleanup_mtx,
(TAILQ_FIRST(&event_handlers)) ? 120 * hz : 0);
/* if timed out, not woken up */
if (rc == EWOULDBLOCK)
check_event_handlers();
}
mutex_exit(&iscsi_cleanup_mtx);
/*
* Wait for all event handlers to deregister, but don't wait more
* than 1 minute (assume registering app has died if it takes longer).
*/
mutex_enter(&iscsi_cleanup_mtx);
for (s = 0; TAILQ_FIRST(&event_handlers) != NULL && s < 60; s++)
kpause("waiteventclr", true, hz, &iscsi_cleanup_mtx);
mutex_exit(&iscsi_cleanup_mtx);