/* * 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 src/sun_nws/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 src/sun_nws/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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "@(#)iscsit_sess.c 1.2 08/03/26 SMI" #include #include #include #include #include #include #include #include #include #include /* networking stuff */ #include /* networking stuff */ #include /* ASSERT3U */ #include #include #include #include #include #include typedef struct { list_node_t se_ctx_node; iscsit_session_event_t se_ctx_event; iscsit_conn_t *se_event_data; } sess_event_ctx_t; extern char *iscsit_ss_name[]; extern char *iscsit_se_name[]; static uint16_t iscsit_get_tsih(void); static void sess_sm_event_locked(iscsit_sess_t *ist, iscsit_session_event_t event, iscsit_conn_t *ict); static void sess_sm_complete(void *ist_void); static void sess_sm_event_dispatch(iscsit_sess_t *ist, sess_event_ctx_t *ctx); static void sess_sm_q1_free(iscsit_sess_t *ist, sess_event_ctx_t *ctx); static void sess_sm_q2_active(iscsit_sess_t *ist, sess_event_ctx_t *ctx); static void sess_sm_q3_logged_in(iscsit_sess_t *ist, sess_event_ctx_t *ctx); static void sess_sm_q4_failed(iscsit_sess_t *ist, sess_event_ctx_t *ctx); static void sess_sm_q5_continue(iscsit_sess_t *ist, sess_event_ctx_t *ctx); static void sess_sm_q6_done(iscsit_sess_t *ist, sess_event_ctx_t *ctx); static void sess_sm_q7_error(iscsit_sess_t *ist, sess_event_ctx_t *ctx); static void sess_sm_new_state(iscsit_sess_t *ist, sess_event_ctx_t *ctx, iscsit_session_state_t new_state); static uint16_t iscsit_tsih_alloc(void) { uintptr_t result; result = (uintptr_t)vmem_alloc(iscsit_global.global_tsih_pool, 1, VM_NOSLEEP | VM_NEXTFIT); /* ISCSI_UNSPEC_TSIH (0) indicates failure */ return (result > ISCSI_MAX_TSIH ? ISCSI_UNSPEC_TSIH : result); } static void iscsit_tsih_free(uint16_t tsih) { vmem_free(iscsit_global.global_tsih_pool, (void *)(uintptr_t)tsih, 1); } iscsit_sess_t * iscsit_sess_create(iscsit_tgt_t *tgt, iscsit_conn_t *ict, uint32_t cmdsn, uint8_t *isid, char *initiator_name, char *target_name, uint8_t *error_class, uint8_t *error_detail) { iscsit_sess_t *result; /* * Even if this session create "fails" for some reason we still need * to return a valid session pointer so that we can send the failed * login response. */ result = kmem_zalloc(sizeof (*result), KM_SLEEP); /* Allocate TSIH */ if ((result->ist_tsih = iscsit_tsih_alloc()) == ISCSI_UNSPEC_TSIH) { /* Out of TSIH's */ *error_class = ISCSI_STATUS_CLASS_TARGET_ERR; *error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; /* * Continue initializing this session so we can use it * to complete the login process. */ } mutex_init(&result->ist_mutex, NULL, MUTEX_DEFAULT, NULL); cv_init(&result->ist_cv, NULL, CV_DEFAULT, NULL); list_create(&result->ist_events, sizeof (sess_event_ctx_t), offsetof(sess_event_ctx_t, se_ctx_node)); list_create(&result->ist_conn_list, sizeof (iscsit_conn_t), offsetof(iscsit_conn_t, ict_sess_ln)); result->ist_state = SS_Q1_FREE; result->ist_last_state = SS_Q1_FREE; bcopy(isid, result->ist_isid, ISCSI_ISID_LEN); result->ist_tgt = tgt; result->ist_expcmdsn = cmdsn + 1; result->ist_maxcmdsn = result->ist_expcmdsn + 1; result->ist_initiator_name = kmem_alloc(strlen(initiator_name) + 1, KM_SLEEP); strcpy(result->ist_initiator_name, initiator_name); if (target_name) { /* A discovery session might not have a target name */ result->ist_target_name = kmem_alloc(strlen(target_name) + 1, KM_SLEEP); strcpy(result->ist_target_name, target_name); } /* Login code will fill in ist_stmf_sess if necessary */ /* XXX set the authentication parameter */ iscsit_sess_set_auth(result); /* Kick session state machine (also binds connection to session) */ iscsit_sess_sm_event(result, SE_CONN_IN_LOGIN, ict); *error_class = ISCSI_STATUS_CLASS_SUCCESS; sess_create_fail: /* * As noted above we must return a session pointer even if something * failed. The resources will get freed later. */ return (result); } void iscsit_sess_destroy(iscsit_sess_t *ist) { if (ist->ist_initiator_name) kmem_free(ist->ist_initiator_name, strlen(ist->ist_initiator_name) + 1); if (ist->ist_target_name) kmem_free(ist->ist_target_name, strlen(ist->ist_target_name) + 1); list_destroy(&ist->ist_conn_list); list_destroy(&ist->ist_events); cv_destroy(&ist->ist_cv); mutex_destroy(&ist->ist_mutex); if (ist->ist_tsih != ISCSI_UNSPEC_TSIH) { iscsit_tsih_free(ist->ist_tsih); } kmem_free(ist, sizeof (*ist)); } void iscsit_sess_bind_conn(iscsit_sess_t *ist, iscsit_conn_t *ict) { ict->ict_sess = ist; mutex_enter(&ist->ist_mutex); ist->ist_conn_count++; list_insert_tail(&ist->ist_conn_list, ict); mutex_exit(&ist->ist_mutex); } void iscsit_sess_unbind_conn(iscsit_sess_t *ist, iscsit_conn_t *ict) { mutex_enter(&ist->ist_mutex); list_remove(&ist->ist_conn_list, ict); ist->ist_conn_count--; mutex_exit(&ist->ist_mutex); } iscsit_conn_t * iscsit_sess_lookup_conn(iscsit_sess_t *ist, uint16_t cid) { iscsit_conn_t *result; for (result = list_head(&ist->ist_conn_list); result != NULL; result = list_next(&ist->ist_conn_list, result)) { if (result->ict_cid == cid) return (result); } return (NULL); } /* * Set the authentication parameters for the session. */ void iscsit_sess_set_auth(iscsit_sess_t *ist) { char *username, *username_in; char *password, *password_in; /* XXX Load target CHAP name */ username = "target"; (void) strcpy(ist->ist_auth.username, username); /* XXX Load target CHAP secret */ password = "987654321098"; (void) strcpy((char *)ist->ist_auth.password, password); ist->ist_auth.password_length = strlen(password); /* XXX Load initiator CHAP name */ username_in = "initiator"; (void) strcpy(ist->ist_auth.username_in, username_in); /* XXX Load initiator CHAP secret */ password_in = "123456789012"; (void) strcpy((char *)ist->ist_auth.password_in, password_in); ist->ist_auth.password_length_in = strlen(password_in); /* XXX load the applicable authentication methods */ if (ist->ist_auth.password_length_in != 0) { ist->ist_auth.authMethodValidList[0] = AM_CHAP; } else { ist->ist_auth.authMethodValidList[0] = AM_NONE; } ist->ist_auth.authMethodValidList[1] = 0; /* end of list */ } iscsit_sess_t * iscsit_sess_reinstate(iscsit_sess_t *ist, iscsit_conn_t *ict, uint8_t *error_class, uint8_t *error_detail) { iscsit_sess_t *new_sess; boolean_t error = B_FALSE; if (ist->ist_state < SS_Q3_LOGGED_IN) { /* Reinstatement is not valid at this point */ *error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR; *error_detail = ISCSI_LOGIN_STATUS_INIT_ERR; /* * Continue with session initialization since we need to * return a session */ error = B_TRUE; } /* * Session reinstatement replaces a current session in * SS_Q4_FAILED state with a new session. The new * session will have the same ISID as the existing session. */ new_sess = iscsit_sess_create(ist->ist_tgt, ict, 0, ist->ist_isid, ist->ist_initiator_name, ist->ist_target_name, error_class, error_detail); ASSERT(new_sess != NULL); /* Copy additional fields from original session */ new_sess->ist_expcmdsn = ist->ist_expcmdsn; new_sess->ist_maxcmdsn = ist->ist_expcmdsn + 1; if (!error) { mutex_enter(&ist->ist_mutex); /* * Generate reinstate event */ sess_sm_event_locked(ist, SE_SESSION_REINSTATE, NULL); mutex_exit(&ist->ist_mutex); } return (new_sess); } int iscsit_sess_avl_compare(const void *void_sess1, const void *void_sess2) { const iscsit_sess_t *sess1 = void_sess1; const iscsit_sess_t *sess2 = void_sess2; int result; /* * Sort by initiator name, then ISID then TSIH */ result = strcmp(sess1->ist_initiator_name, sess2->ist_initiator_name); if (result < 0) { return (-1); } else if (result > 0) { return (1); } /* * Initiator names match, compare ISIDs */ result = memcmp(sess1->ist_isid, sess2->ist_isid, ISCSI_ISID_LEN); if (result < 0) { return (-1); } else if (result > 0) { return (1); } /* * ISIDs match, compare TSIHs */ if (sess1->ist_tsih < sess2->ist_tsih) { return (-1); } else if (sess1->ist_tsih > sess2->ist_tsih) { return (1); } /* * Sessions match */ return (0); } /* * State machine */ void iscsit_sess_sm_event(iscsit_sess_t *ist, iscsit_session_event_t event, iscsit_conn_t *ict) { mutex_enter(&ist->ist_mutex); sess_sm_event_locked(ist, event, ict); mutex_exit(&ist->ist_mutex); } static void sess_sm_event_locked(iscsit_sess_t *ist, iscsit_session_event_t event, iscsit_conn_t *ict) { sess_event_ctx_t *ctx; ctx = kmem_zalloc(sizeof (*ctx), KM_SLEEP); ctx->se_ctx_event = event; ctx->se_event_data = ict; list_insert_tail(&ist->ist_events, ctx); /* * Use the icl_busy flag to keep the state machine single threaded. * This also serves as recursion avoidance since this flag will * always be set if we call login_sm_event from within the * state machine code. */ if (!ist->ist_sm_busy) { ist->ist_sm_busy = B_TRUE; while (!list_is_empty(&ist->ist_events)) { ctx = list_head(&ist->ist_events); list_remove(&ist->ist_events, ctx); mutex_exit(&ist->ist_mutex); sess_sm_event_dispatch(ist, ctx); mutex_enter(&ist->ist_mutex); } ist->ist_sm_busy = B_FALSE; /* * When the state machine reaches SS_Q6_DONE or SS_Q7_ERROR * state the session is over and it's time to cleanup. The * state machine code will mark itself "complete" when this * happens. * * If we get an "spurious" events after we reach SS_Q6_DONE * or SS_Q7_ERROR we don't want to destroy again so * set ist_sm_busy again (shouldn't happen). */ if (ist->ist_sm_complete) { ist->ist_sm_busy = B_TRUE; (void) taskq_dispatch( iscsit_global.global_dispatch_taskq, sess_sm_complete, ist, DDI_SLEEP); } } } static void sess_sm_complete(void *ist_void) { iscsit_sess_t *ist = ist_void; /* * State machine has run to completion, destroy session * * If we have an associated STMF session we should clean it * up now. */ mutex_enter(&ist->ist_mutex); ASSERT(ist->ist_conn_count == 0); if (ist->ist_stmf_sess != NULL) { stmf_deregister_scsi_session(ist->ist_lport, ist->ist_stmf_sess); kmem_free(ist->ist_stmf_sess->ss_rport_id, sizeof (scsi_devid_desc_t) + strlen(ist->ist_initiator_name) + 1); stmf_free(ist->ist_stmf_sess); } mutex_exit(&ist->ist_mutex); iscsit_sess_destroy(ist); } static void sess_sm_event_dispatch(iscsit_sess_t *ist, sess_event_ctx_t *ctx) { iscsit_conn_t *ict; /* State independent actions */ switch (ctx->se_ctx_event) { case SE_CONN_IN_LOGIN: ict = ctx->se_event_data; iscsit_sess_bind_conn(ist, ict); break; case SE_CONN_FAIL: ict = ctx->se_event_data; iscsit_sess_unbind_conn(ist, ict); iscsit_conn_destroy_done(ict); break; } /* State dependent actions */ switch (ist->ist_state) { case SS_Q1_FREE: sess_sm_q1_free(ist, ctx); break; case SS_Q2_ACTIVE: sess_sm_q2_active(ist, ctx); break; case SS_Q3_LOGGED_IN: sess_sm_q3_logged_in(ist, ctx); break; case SS_Q4_FAILED: sess_sm_q4_failed(ist, ctx); break; case SS_Q5_CONTINUE: sess_sm_q5_continue(ist, ctx); break; case SS_Q6_DONE: sess_sm_q6_done(ist, ctx); break; case SS_Q7_ERROR: sess_sm_q7_error(ist, ctx); break; default: ASSERT(0); break; } kmem_free(ctx, sizeof (*ctx)); } static void sess_sm_q1_free(iscsit_sess_t *ist, sess_event_ctx_t *ctx) { switch (ctx->se_ctx_event) { case SE_CONN_IN_LOGIN: /* N1 */ sess_sm_new_state(ist, ctx, SS_Q2_ACTIVE); break; default: ASSERT(0); break; } } static void sess_sm_q2_active(iscsit_sess_t *ist, sess_event_ctx_t *ctx) { switch (ctx->se_ctx_event) { case SE_CONN_LOGGED_IN: /* N2 track FFP connections */ ist->ist_ffp_conn_count++; sess_sm_new_state(ist, ctx, SS_Q3_LOGGED_IN); break; case SE_CONN_IN_LOGIN: /* N2.1, don't care stay in this state */ break; case SE_CONN_FAIL: /* N9 */ sess_sm_new_state(ist, ctx, SS_Q7_ERROR); break; default: ASSERT(0); break; } } static void sess_sm_q3_logged_in(iscsit_sess_t *ist, sess_event_ctx_t *ctx) { iscsit_conn_t *ict; switch (ctx->se_ctx_event) { case SE_CONN_IN_LOGIN: case SE_CONN_FAIL: /* N2.2, don't care */ break; case SE_CONN_LOGGED_IN: /* N2.2, track FFP connections */ ist->ist_ffp_conn_count++; break; case SE_CONN_FFP_FAIL: /* * Event data from event context is the associated connection * which in this case happens to be the last FFP connection * for the session. In certain cases we need to refer * to this last valid connection (i.e. RFC3720 section 12.16) * so we'll save off a pointer here for later use. */ ASSERT(ist->ist_ffp_conn_count >= 1); ist->ist_failed_conn = (iscsit_conn_t *)ctx->se_event_data; ist->ist_ffp_conn_count--; if (ist->ist_ffp_conn_count == 0) { /* N5 */ sess_sm_new_state(ist, ctx, SS_Q4_FAILED); } break; case SE_SESSION_CLOSE: case SE_SESSION_REINSTATE: /* N3 */ mutex_enter(&ist->ist_mutex); for (ict = list_head(&ist->ist_conn_list); ict != NULL; ict = list_next(&ist->ist_conn_list, ict)) { idm_conn_event(ict->ict_ic, CE_LOGOUT_SESSION_SUCCESS, NULL); } mutex_exit(&ist->ist_mutex); sess_sm_new_state(ist, ctx, SS_Q6_DONE); break; default: ASSERT(0); break; } } static void sess_sm_timeout(void *arg) { iscsit_sess_t *ist = arg; iscsit_sess_sm_event(ist, SE_SESSION_TIMEOUT, NULL); } static void sess_sm_q4_failed(iscsit_sess_t *ist, sess_event_ctx_t *ctx) { /* Session timer must not be running when we leave this event */ switch (ctx->se_ctx_event) { case SE_CONN_IN_LOGIN: /* N7 */ sess_sm_new_state(ist, ctx, SS_Q5_CONTINUE); break; case SE_SESSION_REINSTATE: /* N6 */ untimeout(ist->ist_state_timeout); /*FALLTHROUGH*/ case SE_SESSION_TIMEOUT: /* N6 */ sess_sm_new_state(ist, ctx, SS_Q6_DONE); break; case SE_CONN_FAIL: /* Don't care */ break; default: ASSERT(0); break; } } static void sess_sm_q5_continue(iscsit_sess_t *ist, sess_event_ctx_t *ctx) { switch (ctx->se_ctx_event) { case SE_CONN_FAIL: /* N5 */ sess_sm_new_state(ist, ctx, SS_Q4_FAILED); break; case SE_CONN_LOGGED_IN: /* N10 */ sess_sm_new_state(ist, ctx, SS_Q3_LOGGED_IN); break; case SE_SESSION_REINSTATE: /* N11 */ sess_sm_new_state(ist, ctx, SS_Q6_DONE); break; default: ASSERT(0); break; } } static void sess_sm_q6_done(iscsit_sess_t *ist, sess_event_ctx_t *ctx) { /* Terminal state */ switch (ctx->se_ctx_event) { case SE_CONN_FAIL: if (ist->ist_conn_count == 0) { ist->ist_sm_complete = B_TRUE; } break; default: break; } } static void sess_sm_q7_error(iscsit_sess_t *ist, sess_event_ctx_t *ctx) { /* Terminal state */ switch (ctx->se_ctx_event) { case SE_CONN_FAIL: if (ist->ist_conn_count == 0) { ist->ist_sm_complete = B_TRUE; } break; default: break; } } static void sess_sm_new_state(iscsit_sess_t *ist, sess_event_ctx_t *ctx, iscsit_session_state_t new_state) { int t2r_secs; /* * Validate new state */ ASSERT(new_state != SS_UNDEFINED); ASSERT3U(new_state, <, SS_MAX_STATE); new_state = (new_state < SS_MAX_STATE) ? new_state : SS_UNDEFINED; cmn_err(CE_NOTE, "sess_sm_new_state: sess %p, evt %s(%d), " "%s(%d) --> %s(%d)\n", ist, iscsit_se_name[ctx->se_ctx_event], ctx->se_ctx_event, iscsit_ss_name[ist->ist_state], ist->ist_state, iscsit_ss_name[new_state], new_state); DTRACE_PROBE3(sess__state__change, iscsit_sess_t *, ist, sess_event_ctx_t *, ctx, iscsit_session_state_t, new_state); mutex_enter(&ist->ist_mutex); ist->ist_last_state = ist->ist_state; ist->ist_state = new_state; mutex_exit(&ist->ist_mutex); switch (ist->ist_state) { case SS_Q1_FREE: break; case SS_Q2_ACTIVE: iscsit_tgt_bind_sess(ist->ist_tgt, ist); break; case SS_Q3_LOGGED_IN: break; case SS_Q4_FAILED: t2r_secs = ist->ist_failed_conn->ict_op.op_default_time_2_retain; ist->ist_state_timeout = timeout(sess_sm_timeout, ist, drv_usectohz(t2r_secs*1000000)); break; case SS_Q5_CONTINUE: break; case SS_Q6_DONE: case SS_Q7_ERROR: iscsit_tgt_unbind_sess(ist->ist_tgt, ist); if (ist->ist_conn_count == 0) { ist->ist_sm_complete = B_TRUE; } break; default: ASSERT(0); /*NOTREACHED*/ } }