/* * iSCSI Session Management and Slow-path Control * * Copyright (C) 2004 Dmitry Yusupov, Alex Aizman * Copyright (C) 2006 Mike Christie * Copyright (C) 2006 Red Hat, Inc. All rights reserved. * maintained by open-iscsi@googlegroups.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * See the file COPYING included with this distribution for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include "initiator.h" #include "transport.h" #include "iscsid.h" #include "iscsi_if.h" #include "mgmt_ipc.h" #include "event_poll.h" #include "iscsi_ipc.h" #include "idbm.h" #include "log.h" #include "iscsi_util.h" #include "scsi.h" #include "iscsi_sysfs.h" #include "iscsi_settings.h" #include "iface.h" #include "host.h" #include "sysdeps.h" #include "iscsi_err.h" #include "kern_err_table.h" #define ISCSI_CONN_ERR_REOPEN_DELAY 3 #define ISCSI_INTERNAL_ERR_REOPEN_DELAY 5 #define PROC_DIR "/proc" struct login_task_retry_info { actor_t retry_actor; queue_task_t *qtask; node_rec_t *rec; int retry_count; }; static void iscsi_login_timedout(void *data); static int iscsi_sched_ev_context(struct iscsi_ev_context *ev_context, struct iscsi_conn *conn, unsigned long tmo, int event); static int queue_session_login_task_retry(struct login_task_retry_info *info, node_rec_t *rec, queue_task_t *qtask); static int iscsi_ev_context_alloc(iscsi_conn_t *conn) { int i; for (i = 0; i < CONTEXT_POOL_MAX; i++) { conn->context_pool[i] = calloc(1, sizeof(struct iscsi_ev_context) + ipc->ctldev_bufmax); if (!conn->context_pool[i]) { int j; for (j = 0; j < i; j++) free(conn->context_pool[j]); return ENOMEM; } conn->context_pool[i]->conn = conn; } return 0; } static void iscsi_ev_context_free(iscsi_conn_t *conn) { int i; for (i = 0; i < CONTEXT_POOL_MAX; i++) { if (!conn->context_pool[i]) continue; if (conn->context_pool[i]->allocated) /* missing flush on shutdown */ log_error("BUG: context_pool leak %p", conn->context_pool[i]); free(conn->context_pool[i]); } } static struct iscsi_ev_context * iscsi_ev_context_get(iscsi_conn_t *conn, int ev_size) { struct iscsi_ev_context *ev_context; int i; if (ev_size > ipc->ctldev_bufmax) return NULL; for (i = 0; i < CONTEXT_POOL_MAX; i++) { if (!conn->context_pool[i]) continue; if (!conn->context_pool[i]->allocated) { ev_context = conn->context_pool[i]; memset(&ev_context->actor, 0, sizeof(struct actor)); ev_context->allocated = 1; /* some callers abuse this pointer */ ev_context->data = (void *)ev_context + sizeof(struct iscsi_ev_context); log_debug(7, "get ev context %p", &ev_context->actor); return ev_context; } } return NULL; } static void iscsi_ev_context_put(struct iscsi_ev_context *ev_context) { log_debug(7, "put ev context %p", &ev_context->actor); ev_context->allocated = 0; } static void session_online_devs(int host_no, int sid) { iscsi_sysfs_for_each_device(NULL, host_no, sid, iscsi_sysfs_set_device_online); } static conn_login_status_e __login_response_status(iscsi_conn_t *conn, enum iscsi_login_status login_status) { switch (login_status) { case LOGIN_OK: /* check the status class and detail */ return CONN_LOGIN_SUCCESS; case LOGIN_REDIRECT: return CONN_LOGIN_IMM_REDIRECT_RETRY; case LOGIN_IO_ERROR: case LOGIN_REDIRECTION_FAILED: return CONN_LOGIN_RETRY; default: log_error("Login error (Login status %d) on conn %d", conn->id, login_status); break; } return CONN_LOGIN_FAILED; } static conn_login_status_e __check_iscsi_status_class(iscsi_session_t *session, int cid, uint8_t status_class, uint8_t status_detail) { iscsi_conn_t *conn = &session->conn[cid]; switch (status_class) { case ISCSI_STATUS_CLS_SUCCESS: return CONN_LOGIN_SUCCESS; case ISCSI_STATUS_CLS_REDIRECT: switch (status_detail) { case ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP: return CONN_LOGIN_IMM_RETRY; case ISCSI_LOGIN_STATUS_TGT_MOVED_PERM: /* * for a permanent redirect, we need to update the * failback address */ memset(&conn->failback_saddr, 0, sizeof(struct sockaddr_storage)); conn->failback_saddr = conn->saddr; return CONN_LOGIN_IMM_REDIRECT_RETRY; default: log_error("conn %d login rejected: redirection " "type 0x%x not supported", conn->id, status_detail); return CONN_LOGIN_RETRY; } case ISCSI_STATUS_CLS_INITIATOR_ERR: switch (status_detail) { case ISCSI_LOGIN_STATUS_AUTH_FAILED: log_error("session %d login rejected: Initiator " "failed authentication with target", session->id); return CONN_LOGIN_AUTH_FAILED; case ISCSI_LOGIN_STATUS_TGT_FORBIDDEN: log_error("conn %d login rejected: initiator " "failed authorization with target", conn->id); return CONN_LOGIN_AUTH_FAILED; case ISCSI_LOGIN_STATUS_TGT_NOT_FOUND: log_error("conn %d login rejected: initiator " "error - target not found (%02x/%02x)", conn->id, status_class, status_detail); return CONN_LOGIN_FAILED; case ISCSI_LOGIN_STATUS_NO_VERSION: /* * FIXME: if we handle multiple protocol versions, * before we log an error, try the other supported * versions. */ log_error("conn %d login rejected: incompatible " "version (%02x/%02x), non-retryable, " "giving up", conn->id, status_class, status_detail); return CONN_LOGIN_FAILED; default: log_error("conn %d login rejected: initiator " "error (%02x/%02x)", conn->id, status_class, status_detail); return CONN_LOGIN_FAILED; } case ISCSI_STATUS_CLS_TARGET_ERR: log_error("conn %d login rejected: target error " "(%02x/%02x)", conn->id, status_class, status_detail); /* * We have no idea what the problem is. But spec says initiator * may retry later. */ return CONN_LOGIN_RETRY; default: log_error("conn %d login response with unknown status " "class 0x%x, detail 0x%x", conn->id, status_class, status_detail); break; } return CONN_LOGIN_FAILED; } static int __session_conn_create(iscsi_session_t *session, int cid) { iscsi_conn_t *conn = &session->conn[cid]; conn_rec_t *conn_rec = &session->nrec.conn[cid]; node_rec_t *rec = &session->nrec; int err; if (iscsi_ev_context_alloc(conn)) { log_error("cannot allocate context_pool for conn cid %d", cid); return ISCSI_ERR_NOMEM; } /* set session reconnection retry max */ session->reopen_max = rec->session.reopen_max; conn->state = ISCSI_CONN_STATE_FREE; conn->session = session; actor_init(&conn->login_timer, iscsi_login_timedout, NULL); /* * TODO: we must export the socket_fd/transport_eph from sysfs * so if iscsid is resyncing up we can pick that up and cleanup up * the old connection. Right now we leak a connection. * We can also probably merge these two fields. */ conn->socket_fd = -1; conn->transport_ep_handle = -1; /* connection's timeouts */ conn->id = cid; conn->logout_timeout = conn_rec->timeo.logout_timeout; if (!conn->logout_timeout) { log_error("Invalid timeo.logout_timeout. Must be greater " "than zero. Using default %d.", DEF_LOGOUT_TIMEO); conn->logout_timeout = DEF_LOGOUT_TIMEO; } conn->login_timeout = conn_rec->timeo.login_timeout; if (!conn->login_timeout) { log_error("Invalid timeo.login_timeout. Must be greater " "than zero. Using default %d.", DEF_LOGIN_TIMEO); conn->login_timeout = DEF_LOGIN_TIMEO; } conn->auth_timeout = conn_rec->timeo.auth_timeout; /* noop-out setting */ conn->noop_out_interval = conn_rec->timeo.noop_out_interval; conn->noop_out_timeout = conn_rec->timeo.noop_out_timeout; if (conn->noop_out_interval && !conn->noop_out_timeout) { log_error("Invalid timeo.noop_out_timeout. Must be greater " "than zero. Using default %d.", DEF_NOOP_OUT_TIMEO); conn->noop_out_timeout = DEF_NOOP_OUT_TIMEO; } if (conn->noop_out_timeout && !conn->noop_out_interval) { log_error("Invalid timeo.noop_out_interval. Must be greater " "than zero. Using default %d.", DEF_NOOP_OUT_INTERVAL); conn->noop_out_interval = DEF_NOOP_OUT_INTERVAL; } iscsi_copy_operational_params(conn, &session->nrec.session.iscsi, &conn_rec->iscsi); /* TCP options */ conn->tcp_window_size = conn_rec->tcp.window_size; /* FIXME: type_of_service */ /* resolve the string address to an IP address */ err = iscsi_setup_portal(conn, conn_rec->address, conn_rec->port); if (err) return err; return 0; } static void session_release(iscsi_session_t *session) { log_debug(2, "Releasing session %p", session); if (session->target_alias) free(session->target_alias); iscsi_ev_context_free(&session->conn[0]); free(session); } static iscsi_session_t* __session_create(node_rec_t *rec, struct iscsi_transport *t, int *rc) { iscsi_session_t *session; int hostno; *rc = 0; session = calloc(1, sizeof (*session)); if (session == NULL) { log_debug(1, "can not allocate memory for session"); *rc = ISCSI_ERR_NOMEM; return NULL; } log_debug(2, "Allocted session %p", session); INIT_LIST_HEAD(&session->list); session->t = t; session->reopen_qtask.mgmt_ipc_fd = -1; session->id = INVALID_SESSION_ID; session->use_ipc = 1; /* save node record. we might need it for redirection */ memcpy(&session->nrec, rec, sizeof(node_rec_t)); session->portal_group_tag = rec->tpgt; session->type = ISCSI_SESSION_TYPE_NORMAL; session->r_stage = R_STAGE_NO_CHANGE; strlcpy(session->target_name, rec->name, TARGET_NAME_MAXLEN); if (strlen(session->nrec.iface.iname)) session->initiator_name = session->nrec.iface.iname; else if (dconfig->initiator_name) session->initiator_name = dconfig->initiator_name; else { log_error("No initiator name set. Cannot create session."); *rc = ISCSI_ERR_INVAL; goto free_session; } if (strlen(session->nrec.iface.alias)) session->initiator_alias = session->nrec.iface.alias; else session->initiator_alias = dconfig->initiator_alias; /* session's eh parameters */ session->replacement_timeout = rec->session.timeo.replacement_timeout; session->fast_abort = rec->session.iscsi.FastAbort; session->abort_timeout = rec->session.err_timeo.abort_timeout; session->lu_reset_timeout = rec->session.err_timeo.lu_reset_timeout; session->tgt_reset_timeout = rec->session.err_timeo.tgt_reset_timeout; session->host_reset_timeout = rec->session.err_timeo.host_reset_timeout; /* OUI and uniqifying number */ session->isid[0] = DRIVER_ISID_0; session->isid[1] = DRIVER_ISID_1; session->isid[2] = DRIVER_ISID_2; session->isid[3] = 0; session->isid[4] = 0; session->isid[5] = 0; /* setup authentication variables for the session*/ iscsi_setup_authentication(session, &rec->session.auth); iscsi_session_init_params(session); if (t->template->bind_ep_required) { hostno = iscsi_sysfs_get_host_no_from_hwinfo(&rec->iface, rc); if (!*rc) { /* * if the netdev or mac was set, then we are going to want * to want to bind the all the conns/eps to a specific host * if offload is used. */ session->conn[0].bind_ep = 1; session->hostno = hostno; } else if (*rc == ISCSI_ERR_HOST_NOT_FOUND) { goto free_session; } else { *rc = 0; } } /* reset session reopen count */ session->reopen_cnt = 0; list_add_tail(&session->list, &t->sessions); return session; free_session: free(session); return NULL; } static void iscsi_flush_context_pool(struct iscsi_session *session) { struct iscsi_ev_context *ev_context; struct iscsi_conn *conn = &session->conn[0]; int i; for (i = 0; i < CONTEXT_POOL_MAX; i++) { ev_context = conn->context_pool[i]; if (!ev_context) continue; if (ev_context->allocated) { actor_delete(&(conn->context_pool[i]->actor)); iscsi_ev_context_put(ev_context); } } } static void __session_destroy(iscsi_session_t *session) { log_debug(1, "destroying session"); list_del(&session->list); iscsi_flush_context_pool(session); session_release(session); } static void conn_delete_timers(iscsi_conn_t *conn) { actor_delete(&conn->login_timer); actor_delete(&conn->nop_out_timer); } static int session_conn_shutdown(iscsi_conn_t *conn, queue_task_t *qtask, int err) { iscsi_session_t *session = conn->session; log_debug(2, "disconnect conn"); /* this will check for a valid interconnect connection */ if (session->t->template->ep_disconnect) session->t->template->ep_disconnect(conn); if (session->id == INVALID_SESSION_ID) goto cleanup; if (!iscsi_sysfs_session_has_leadconn(session->id)) goto cleanup; if (conn->state == ISCSI_CONN_STATE_IN_LOGIN || conn->state == ISCSI_CONN_STATE_IN_LOGOUT || conn->state == ISCSI_CONN_STATE_LOGGED_IN) { log_debug(2, "stop conn (conn state %d)", conn->state); if (ipc->stop_conn(session->t->handle, session->id, conn->id, STOP_CONN_TERM)) { log_error("can't stop connection %d:%d (%d)", session->id, conn->id, errno); return ISCSI_ERR_INTERNAL; } } log_debug(2, "kdestroy conn"); if (ipc->destroy_conn(session->t->handle, session->id, conn->id)) { log_error("can not safely destroy connection %d", conn->id); return ISCSI_ERR_INTERNAL; } cleanup: if (session->id != INVALID_SESSION_ID) { log_debug(2, "kdestroy session %u", session->id); session->r_stage = R_STAGE_SESSION_DESTOYED; if (ipc->destroy_session(session->t->handle, session->id)) { log_error("can not safely destroy session %d", session->id); return ISCSI_ERR_INTERNAL; } } log_warning("Connection%d:%d to [target: %s, portal: %s,%d] " "through [iface: %s] is shutdown.", session->id, conn->id, session->nrec.name, session->nrec.conn[conn->id].address, session->nrec.conn[conn->id].port, session->nrec.iface.name); mgmt_ipc_write_rsp(qtask, err); conn_delete_timers(conn); __session_destroy(session); return ISCSI_SUCCESS; } static void queue_delayed_reopen(queue_task_t *qtask, int delay) { iscsi_conn_t *conn = qtask->conn; log_debug(4, "Requeue reopen attempt in %d secs", delay); /* * iscsi_login_eh can handle the login resched as * if it were login time out */ actor_timer_mod(&conn->login_timer, delay, qtask); } static int iscsi_conn_connect(struct iscsi_conn *conn, queue_task_t *qtask) { struct iscsi_ev_context *ev_context; int rc; ev_context = iscsi_ev_context_get(conn, 0); if (!ev_context) { /* while reopening the recv pool should be full */ log_error("BUG: __session_conn_reopen could not get conn " "context for recv."); return ENOMEM; } ev_context->data = qtask; rc = conn->session->t->template->ep_connect(conn, 1); if (rc < 0 && errno != EINPROGRESS) { char serv[NI_MAXSERV]; getnameinfo((struct sockaddr *) &conn->saddr, sizeof(conn->saddr), conn->host, sizeof(conn->host), serv, sizeof(serv), NI_NUMERICHOST|NI_NUMERICSERV); log_error("cannot make a connection to %s:%s (%d,%d)", conn->host, serv, rc, errno); iscsi_ev_context_put(ev_context); return ENOTCONN; } iscsi_sched_ev_context(ev_context, conn, 0, EV_CONN_POLL); log_debug(3, "Setting login timer %p timeout %d", &conn->login_timer, conn->login_timeout); actor_timer_mod(&conn->login_timer, conn->login_timeout, qtask); return 0; } static void __session_conn_reopen(iscsi_conn_t *conn, queue_task_t *qtask, int do_stop, int redirected) { iscsi_session_t *session = conn->session; uint32_t delay = 0; log_debug(1, "re-opening session %d (reopen_cnt %d)", session->id, session->reopen_cnt); qtask->conn = conn; /* flush stale polls or errors queued */ iscsi_flush_context_pool(session); conn_delete_timers(conn); conn->state = ISCSI_CONN_STATE_XPT_WAIT; conn->session->t->template->ep_disconnect(conn); if (do_stop) { /* state: ISCSI_CONN_STATE_CLEANUP_WAIT */ if (ipc->stop_conn(session->t->handle, session->id, conn->id, do_stop)) { log_error("can't stop connection %d:%d (%d)", session->id, conn->id, errno); delay = ISCSI_INTERNAL_ERR_REOPEN_DELAY; goto queue_reopen; } log_debug(3, "connection %d:%d is stopped for recovery", session->id, conn->id); } if (!redirected) { delay = session->def_time2wait; session->def_time2wait = 0; if (delay) goto queue_reopen; } if (!redirected) session->reopen_cnt++; /* uIP will needs to be re-triggered on the connection re-open */ if (iscsi_set_net_config(conn->session->t, conn->session, &conn->session->nrec.iface) != 0) goto queue_reopen; if (iscsi_conn_connect(conn, qtask)) { delay = ISCSI_CONN_ERR_REOPEN_DELAY; goto queue_reopen; } return; queue_reopen: log_debug(4, "Waiting %u seconds before trying to reconnect.", delay); queue_delayed_reopen(qtask, delay); } static void session_conn_reopen(iscsi_conn_t *conn, queue_task_t *qtask, int do_stop) { /* * If we were temporarily redirected, we need to fall back to * the original address to see where the target will send us * for the retry */ memset(&conn->saddr, 0, sizeof(struct sockaddr_storage)); conn->saddr = conn->failback_saddr; __session_conn_reopen(conn, qtask, do_stop, 0); } static int iscsi_retry_initial_login(struct iscsi_conn *conn) { int initial_login_retry_max; struct timeval now, timeout, fail_time; initial_login_retry_max = conn->session->nrec.session.initial_login_retry_max; memset(&now, 0, sizeof(now)); memset(&timeout, 0, sizeof(timeout)); memset(&fail_time, 0, sizeof(fail_time)); timeout.tv_sec = initial_login_retry_max * conn->login_timeout; if (gettimeofday(&now, NULL)) { log_error("Could not get time of day. Dropping down to " "max retry check."); return initial_login_retry_max > conn->session->reopen_cnt; } timeradd(&conn->initial_connect_time, &timeout, &fail_time); /* * if we have been trying for login_retry_max * login_timeout * then it is time to give up */ if (timercmp(&now, &fail_time, >)) { log_debug(1, "Giving up on initial login attempt after " "%u seconds.", initial_login_retry_max * conn->login_timeout); return 0; } return 1; } static int iscsi_login_is_fatal_err(int err) { if (err == ISCSI_ERR_LOGIN_AUTH_FAILED || err == ISCSI_ERR_FATAL_LOGIN) return 1; return 0; } static void iscsi_login_eh(struct iscsi_conn *conn, struct queue_task *qtask, int err) { struct iscsi_session *session = conn->session; int stop_flag = 0; log_debug(3, "iscsi_login_eh"); /* * Flush polls and other events */ iscsi_flush_context_pool(conn->session); switch (conn->state) { case ISCSI_CONN_STATE_XPT_WAIT: switch (session->r_stage) { case R_STAGE_NO_CHANGE: log_debug(6, "login failed ISCSI_CONN_STATE_XPT_WAIT/" "R_STAGE_NO_CHANGE"); /* timeout during initial connect. * clean connection. write ipc rsp or retry */ if (iscsi_login_is_fatal_err(err) || !iscsi_retry_initial_login(conn)) session_conn_shutdown(conn, qtask, err); else { stop_flag = (session->id < INVALID_SESSION_ID) ? STOP_CONN_TERM : 0; log_debug(6, "connection %p socket_fd: %d, " "session id: %d stop_flag: %d\n", conn, conn->socket_fd, session->id, stop_flag); session_conn_reopen(conn, qtask, stop_flag); } break; case R_STAGE_SESSION_REDIRECT: log_debug(6, "login failed ISCSI_CONN_STATE_XPT_WAIT/" "R_STAGE_SESSION_REDIRECT"); /* timeout during initial redirect connect * clean connection. write ipc rsp or retry */ if (iscsi_login_is_fatal_err(err) || !iscsi_retry_initial_login(conn)) session_conn_shutdown(conn, qtask, err); else session_conn_reopen(conn, qtask, 0); break; case R_STAGE_SESSION_REOPEN: log_debug(6, "login failed ISCSI_CONN_STATE_XPT_WAIT/" "R_STAGE_SESSION_REOPEN (reopen_cnt=%d, reopen_max=%d)", session->reopen_cnt, session->reopen_max); if (session->reopen_max && (session->reopen_cnt > session->reopen_max)) { log_info("Giving up on session %d after %d retries", session->id, session->reopen_max); session_conn_shutdown(conn, qtask, err); break; } /* timeout during reopen connect. try again */ session_conn_reopen(conn, qtask, 0); break; case R_STAGE_SESSION_CLEANUP: session_conn_shutdown(conn, qtask, err); break; default: break; } break; case ISCSI_CONN_STATE_IN_LOGIN: switch (session->r_stage) { case R_STAGE_NO_CHANGE: case R_STAGE_SESSION_REDIRECT: log_debug(6, "login failed ISCSI_CONN_STATE_IN_LOGIN/" "R_STAGE_NO_CHANGE %d", session->reopen_cnt); /* * send pdu timeout during initial connect or * initial redirected connect. Clean connection * and write rsp or retry. */ if (iscsi_login_is_fatal_err(err) || !iscsi_retry_initial_login(conn)) session_conn_shutdown(conn, qtask, err); else session_conn_reopen(conn, qtask, STOP_CONN_RECOVER); break; case R_STAGE_SESSION_REOPEN: log_debug(6, "login failed ISCSI_CONN_STATE_IN_LOGIN/" "R_STAGE_SESSION_REOPEN %d", session->reopen_cnt); session_conn_reopen(conn, qtask, STOP_CONN_RECOVER); break; case R_STAGE_SESSION_CLEANUP: session_conn_shutdown(conn, qtask, ISCSI_ERR_PDU_TIMEOUT); break; default: break; } break; default: log_error("Ignoring login error %d in conn state %d.", err, conn->state); break; } } static void __conn_error_handle(iscsi_session_t *session, iscsi_conn_t *conn) { int i; /* * if we got an error while trying to logout for the user then * just cleanup and return to the user. */ if (conn->logout_qtask) { session_conn_shutdown(conn, conn->logout_qtask, ISCSI_SUCCESS); return; } switch (conn->state) { case ISCSI_CONN_STATE_IN_LOGOUT: /* logout was from eh - fall down to cleanup */ case ISCSI_CONN_STATE_LOGGED_IN: /* mark failed connection */ conn->state = ISCSI_CONN_STATE_CLEANUP_WAIT; if (session->erl > 0) { /* check if we still have some logged in connections */ for (i=0; iconn[i].state == ISCSI_CONN_STATE_LOGGED_IN) break; } if (i != ISCSI_CONN_MAX) { /* FIXME: re-assign leading connection * for ERL>0 */ } break; } /* mark all connections as failed */ for (i=0; iconn[i].state == ISCSI_CONN_STATE_LOGGED_IN) session->conn[i].state = ISCSI_CONN_STATE_CLEANUP_WAIT; } session->r_stage = R_STAGE_SESSION_REOPEN; break; case ISCSI_CONN_STATE_IN_LOGIN: if (session->r_stage == R_STAGE_SESSION_REOPEN) { queue_task_t *qtask; if (session->notify_qtask) qtask = session->notify_qtask; else qtask = &session->reopen_qtask; iscsi_login_eh(conn, qtask, ISCSI_ERR_TRANS); return; } log_debug(1, "ignoring conn error in login. " "let it timeout"); return; case ISCSI_CONN_STATE_XPT_WAIT: log_debug(1, "ignoring conn error in XPT_WAIT. " "let connection fail on its own"); return; case ISCSI_CONN_STATE_CLEANUP_WAIT: log_debug(1, "ignoring conn error in CLEANUP_WAIT. " "let connection stop"); return; default: log_debug(8, "invalid state %d", conn->state); return; } if (session->r_stage == R_STAGE_SESSION_REOPEN) { session_conn_reopen(conn, &session->reopen_qtask, STOP_CONN_RECOVER); return; } } static void session_conn_error(void *data) { struct iscsi_ev_context *ev_context = data; enum iscsi_err error = *(enum iscsi_err *)ev_context->data; iscsi_conn_t *conn = ev_context->conn; iscsi_session_t *session = conn->session; log_warning("Kernel reported iSCSI connection %d:%d error (%d - %s) " "state (%d)", session->id, conn->id, error, kern_err_code_to_string(error), conn->state); iscsi_ev_context_put(ev_context); switch (error) { case ISCSI_ERR_INVALID_HOST: if (session_conn_shutdown(conn, NULL, ISCSI_SUCCESS)) log_error("BUG: Could not shutdown session."); break; default: __conn_error_handle(session, conn); } } static void iscsi_login_timedout(void *data) { struct queue_task *qtask = data; struct iscsi_conn *conn = qtask->conn; switch (conn->state) { case ISCSI_CONN_STATE_XPT_WAIT: iscsi_login_eh(conn, qtask, ISCSI_ERR_TRANS_TIMEOUT); break; case ISCSI_CONN_STATE_IN_LOGIN: iscsi_login_eh(conn, qtask, ISCSI_ERR_PDU_TIMEOUT); break; default: iscsi_login_eh(conn, qtask, ISCSI_ERR_INTERNAL); break; } } static void iscsi_login_redirect(iscsi_conn_t *conn) { iscsi_session_t *session = conn->session; iscsi_login_context_t *c = &conn->login_context; log_debug(3, "login redirect ..."); if (session->r_stage == R_STAGE_NO_CHANGE) session->r_stage = R_STAGE_SESSION_REDIRECT; __session_conn_reopen(conn, c->qtask, STOP_CONN_RECOVER, 1); } static int __send_nopin_rsp(iscsi_conn_t *conn, struct iscsi_nopin *rhdr, char *data) { struct iscsi_nopout hdr; memset(&hdr, 0, sizeof(struct iscsi_nopout)); hdr.opcode = ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE; hdr.flags = ISCSI_FLAG_CMD_FINAL; hdr.dlength[0] = rhdr->dlength[0]; hdr.dlength[1] = rhdr->dlength[1]; hdr.dlength[2] = rhdr->dlength[2]; memcpy(hdr.lun, rhdr->lun, 8); hdr.ttt = rhdr->ttt; hdr.itt = ISCSI_RESERVED_TAG; return iscsi_io_send_pdu(conn, (struct iscsi_hdr*)&hdr, ISCSI_DIGEST_NONE, data, ISCSI_DIGEST_NONE, 0); } static int __send_nopout(iscsi_conn_t *conn) { struct iscsi_nopout hdr; memset(&hdr, 0, sizeof(struct iscsi_nopout)); hdr.opcode = ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE; hdr.flags = ISCSI_FLAG_CMD_FINAL; hdr.itt = 0; /* XXX: let kernel send_pdu set for us*/ hdr.ttt = ISCSI_RESERVED_TAG; /* we have hdr.lun reserved, and no data */ return iscsi_io_send_pdu(conn, (struct iscsi_hdr*)&hdr, ISCSI_DIGEST_NONE, NULL, ISCSI_DIGEST_NONE, 0); } static void conn_nop_out_timeout(void *data) { iscsi_conn_t *conn = (iscsi_conn_t*)data; iscsi_session_t *session = conn->session; log_warning("Nop-out timedout after %d seconds on connection %d:%d " "state (%d). Dropping session.", conn->noop_out_timeout, session->id, conn->id, conn->state); /* XXX: error handle */ __conn_error_handle(session, conn); } static void conn_send_nop_out(void *data) { iscsi_conn_t *conn = data; /* * we cannot start new request during logout and the logout timer * will figure things out. */ if (conn->state == ISCSI_CONN_STATE_IN_LOGOUT) return; __send_nopout(conn); actor_timer(&conn->nop_out_timer, conn->noop_out_timeout, conn_nop_out_timeout, conn); log_debug(3, "noop out timeout timer %p start, timeout %d", &conn->nop_out_timer, conn->noop_out_timeout); } void free_initiator(void) { struct iscsi_transport *t; iscsi_session_t *session, *tmp; list_for_each_entry(t, &transports, list) { list_for_each_entry_safe(session, tmp, &t->sessions, list) { list_del(&session->list); iscsi_flush_context_pool(session); session_release(session); } } free_transports(); } static void session_scan_host(struct iscsi_session *session, int hostno, queue_task_t *qtask) { pid_t pid; pid = iscsi_sysfs_scan_host(hostno, 1, idbm_session_autoscan(session)); if (pid == 0) { mgmt_ipc_write_rsp(qtask, ISCSI_SUCCESS); if (session) iscsi_sysfs_for_each_device( &session->nrec.session.queue_depth, hostno, session->id, iscsi_sysfs_set_queue_depth); exit(0); } else if (pid > 0) { reap_inc(); if (qtask && qtask->mgmt_ipc_fd >= 0) { close(qtask->mgmt_ipc_fd); free(qtask); } } else mgmt_ipc_write_rsp(qtask, ISCSI_ERR_INTERNAL); } static void setup_full_feature_phase(iscsi_conn_t *conn) { iscsi_session_t *session = conn->session; iscsi_login_context_t *c = &conn->login_context; int rc; actor_delete(&conn->login_timer); if (iscsi_session_set_neg_params(conn)) { iscsi_login_eh(conn, c->qtask, ISCSI_ERR_LOGIN); return; } if (ipc->start_conn(session->t->handle, session->id, conn->id, &rc) || rc) { log_error("can't start connection %d:%d retcode %d (%d)", session->id, conn->id, rc, errno); iscsi_login_eh(conn, c->qtask, ISCSI_ERR_INTERNAL); return; } conn->state = ISCSI_CONN_STATE_LOGGED_IN; if (session->r_stage == R_STAGE_NO_CHANGE || session->r_stage == R_STAGE_SESSION_REDIRECT) { /* * scan host is one-time deal. We * don't want to re-scan it on recovery. */ if (conn->id == 0) session_scan_host(session, session->hostno, c->qtask); log_warning("Connection%d:%d to [target: %s, portal: %s,%d] " "through [iface: %s] is operational now", session->id, conn->id, session->nrec.name, session->nrec.conn[conn->id].address, session->nrec.conn[conn->id].port, session->nrec.iface.name); } else { session->notify_qtask = NULL; session_online_devs(session->hostno, session->id); mgmt_ipc_write_rsp(c->qtask, ISCSI_SUCCESS); log_warning("connection%d:%d is operational after recovery " "(%d attempts)", session->id, conn->id, session->reopen_cnt); } /* * reset ERL=0 reopen counter */ session->reopen_cnt = 0; session->r_stage = R_STAGE_NO_CHANGE; /* noop_out */ if (conn->userspace_nop && conn->noop_out_interval) { actor_timer(&conn->nop_out_timer, conn->noop_out_interval, conn_send_nop_out, conn); log_debug(3, "noop out timer %p start", &conn->nop_out_timer); } } static void iscsi_logout_timedout(void *data) { struct iscsi_ev_context *ev_context = data; struct iscsi_conn *conn = ev_context->conn; iscsi_ev_context_put(ev_context); /* * assume we were in ISCSI_CONN_STATE_IN_LOGOUT or there * was some nasty error */ log_debug(3, "logout timeout, dropping conn..."); __conn_error_handle(conn->session, conn); } static int iscsi_send_logout(iscsi_conn_t *conn) { struct iscsi_logout hdr; struct iscsi_ev_context *ev_context; if (conn->state != ISCSI_CONN_STATE_LOGGED_IN) return EINVAL; memset(&hdr, 0, sizeof(struct iscsi_logout)); hdr.opcode = ISCSI_OP_LOGOUT | ISCSI_OP_IMMEDIATE; hdr.flags = ISCSI_FLAG_CMD_FINAL | (ISCSI_LOGOUT_REASON_CLOSE_SESSION & ISCSI_FLAG_LOGOUT_REASON_MASK); /* kernel will set the rest */ if (!iscsi_io_send_pdu(conn, (struct iscsi_hdr*)&hdr, ISCSI_DIGEST_NONE, NULL, ISCSI_DIGEST_NONE, 0)) return EIO; conn->state = ISCSI_CONN_STATE_IN_LOGOUT; ev_context = iscsi_ev_context_get(conn, 0); if (!ev_context) /* unbounded logout */ log_warning("Could not allocate conn context for logout."); else { iscsi_sched_ev_context(ev_context, conn, conn->logout_timeout, EV_CONN_LOGOUT_TIMER); log_debug(3, "logout timeout timer %u", conn->logout_timeout * 1000); } return 0; } static void iscsi_stop(void *data) { struct iscsi_ev_context *ev_context = data; struct iscsi_conn *conn = ev_context->conn; int rc = 0; iscsi_ev_context_put(ev_context); if (!(conn->session->t->caps & CAP_LOGIN_OFFLOAD)) { if (!iscsi_send_logout(conn)) return; } rc = session_conn_shutdown(conn, conn->logout_qtask, ISCSI_SUCCESS); if (rc) log_error("BUG: Could not shutdown session."); } static void iscsi_recv_nop_in(iscsi_conn_t *conn, struct iscsi_hdr *hdr) { if (!conn->userspace_nop) { log_error("Got nop in, but kernel supports nop handling."); return; } if (hdr->ttt == ISCSI_RESERVED_TAG) { /* noop out rsp */ actor_delete(&conn->nop_out_timer); /* schedule a new ping */ actor_timer(&conn->nop_out_timer, conn->noop_out_interval, conn_send_nop_out, conn); } else /* noop in req */ if (!__send_nopin_rsp(conn, (struct iscsi_nopin*)hdr, conn->data)) { log_error("can not send nopin response"); } } static void iscsi_recv_logout_rsp(iscsi_conn_t *conn, struct iscsi_hdr *hdr) { struct iscsi_logout_rsp *logout_rsp = (struct iscsi_logout_rsp *)hdr; log_debug(3, "Recv: logout response %d", logout_rsp->response); if (logout_rsp->response == 2 || logout_rsp->response == 3) { conn->session->def_time2wait = ntohs(logout_rsp->t2wait); log_debug(4, "logout rsp returned time2wait %u", conn->session->def_time2wait); } /* TODO process the hdr */ __conn_error_handle(conn->session, conn); } static void iscsi_recv_async_msg(iscsi_conn_t *conn, struct iscsi_hdr *hdr) { iscsi_session_t *session = conn->session; struct iscsi_async *async_hdr = (struct iscsi_async *)hdr; char *buf = conn->data; unsigned int senselen; struct scsi_sense_hdr sshdr; log_debug(3, "Read AEN %d", async_hdr->async_event); switch (async_hdr->async_event) { case ISCSI_ASYNC_MSG_SCSI_EVENT: senselen = (buf[0] << 8) | buf[1]; buf += 2; if (!scsi_normalize_sense((uint8_t *)buf, senselen, &sshdr)) { log_error("Could not handle AEN %d. Invalid sense.", async_hdr->async_event); break; } if (sshdr.asc == 0x3f && sshdr.ascq == 0x0e && idbm_session_autoscan(session)) session_scan_host(session, session->hostno, NULL); break; case ISCSI_ASYNC_MSG_REQUEST_LOGOUT: log_warning("Target requests logout within %u seconds for " "connection", ntohs(async_hdr->param3)); if (iscsi_send_logout(conn)) log_error("Could not send logout in response to" "logout request aen"); break; case ISCSI_ASYNC_MSG_DROPPING_CONNECTION: log_warning("Target dropping connection %u, reconnect min %u " "max %u", ntohs(async_hdr->param1), ntohs(async_hdr->param2), ntohs(async_hdr->param3)); session->def_time2wait = (uint32_t)ntohs(async_hdr->param2) & 0x0000FFFFFL; break; case ISCSI_ASYNC_MSG_DROPPING_ALL_CONNECTIONS: log_warning("Target dropping all connections, reconnect min %u " "max %u", ntohs(async_hdr->param2), ntohs(async_hdr->param3)); session->def_time2wait = (uint32_t)ntohs(async_hdr->param2) & 0x0000FFFFFL; break; case ISCSI_ASYNC_MSG_PARAM_NEGOTIATION: log_warning("Received async event param negotiation, " "dropping session"); __conn_error_handle(session, conn); break; case ISCSI_ASYNC_MSG_VENDOR_SPECIFIC: default: log_warning("AEN not supported"); } } static void iscsi_recv_login_rsp(struct iscsi_conn *conn) { struct iscsi_session *session = conn->session; iscsi_login_context_t *c = &conn->login_context; int err = ISCSI_ERR_FATAL_LOGIN; if (iscsi_login_rsp(session, c)) { log_debug(1, "login_rsp ret (%d)", c->ret); switch (__login_response_status(conn, c->ret)) { case CONN_LOGIN_FAILED: goto failed; case CONN_LOGIN_RETRY: goto retry; case CONN_LOGIN_IMM_REDIRECT_RETRY: iscsi_login_redirect(conn); return; default: ; /* success - fall through */ } /* check the login status */ switch (__check_iscsi_status_class(session, conn->id, c->status_class, c->status_detail)) { case CONN_LOGIN_AUTH_FAILED: err = ISCSI_ERR_LOGIN_AUTH_FAILED; goto failed; case CONN_LOGIN_FAILED: goto failed; case CONN_LOGIN_IMM_REDIRECT_RETRY: iscsi_login_redirect(conn); return; case CONN_LOGIN_IMM_RETRY: case CONN_LOGIN_RETRY: goto retry; default: ; /* success - fall through */ } } if (conn->current_stage != ISCSI_FULL_FEATURE_PHASE) { /* more nego. needed! */ conn->state = ISCSI_CONN_STATE_IN_LOGIN; if (iscsi_login_req(session, c)) { iscsi_login_eh(conn, c->qtask, ISCSI_ERR_LOGIN); return; } } else setup_full_feature_phase(conn); return; retry: /* retry if not initial login or initial login has not timed out */ iscsi_login_eh(conn, c->qtask, ISCSI_ERR_LOGIN); return; failed: /* force failure if initial login */ session->reopen_cnt = session->nrec.session.initial_login_retry_max; iscsi_login_eh(conn, c->qtask, err); return; } static void session_conn_recv_pdu(void *data) { struct iscsi_ev_context *ev_context = data; iscsi_conn_t *conn = ev_context->conn; struct iscsi_hdr hdr; conn->recv_context = ev_context; switch (conn->state) { case ISCSI_CONN_STATE_IN_LOGIN: iscsi_recv_login_rsp(conn); break; case ISCSI_CONN_STATE_LOGGED_IN: case ISCSI_CONN_STATE_IN_LOGOUT: case ISCSI_CONN_STATE_LOGOUT_REQUESTED: /* read incoming PDU */ if (iscsi_io_recv_pdu(conn, &hdr, ISCSI_DIGEST_NONE, conn->data, ISCSI_DEF_MAX_RECV_SEG_LEN, ISCSI_DIGEST_NONE, 0) < 0) return; switch (hdr.opcode & ISCSI_OPCODE_MASK) { case ISCSI_OP_NOOP_IN: iscsi_recv_nop_in(conn, &hdr); break; case ISCSI_OP_LOGOUT_RSP: iscsi_recv_logout_rsp(conn, &hdr); break; case ISCSI_OP_ASYNC_EVENT: iscsi_recv_async_msg(conn, &hdr); break; default: log_error("unsupported opcode 0x%x", hdr.opcode); break; } break; case ISCSI_CONN_STATE_XPT_WAIT: iscsi_ev_context_put(ev_context); log_debug(1, "ignoring incoming PDU in XPT_WAIT. " "let connection re-establish or fail"); break; case ISCSI_CONN_STATE_CLEANUP_WAIT: iscsi_ev_context_put(ev_context); log_debug(1, "ignoring incoming PDU in XPT_WAIT. " "let connection cleanup"); break; default: iscsi_ev_context_put(ev_context); log_error("Invalid state. Dropping PDU."); } } static void session_increase_wq_priority(struct iscsi_session *session) { DIR *proc_dir; struct dirent *proc_dent; struct stat statb; char stat_file[PATH_SIZE]; char sbuf[1024]; /* got this from ps */ int pid, stat_fd, num_read; char *proc_name, *proc_name_end; uint32_t host_no; /* drivers like bnx2i and qla4xxx do not have a write wq */ if (session->t->caps & CAP_DATA_PATH_OFFLOAD) return; proc_dir = opendir(PROC_DIR); if (!proc_dir) goto fail; while ((proc_dent = readdir(proc_dir))) { if (!strcmp(proc_dent->d_name, ".") || !strcmp(proc_dent->d_name, "..")) continue; if (sscanf(proc_dent->d_name, "%d", &pid) != 1) continue; memset(stat_file, 0, sizeof(stat_file)); sprintf(stat_file, PROC_DIR"/%d/stat", pid); if (stat(stat_file, &statb)) continue; if (!S_ISREG( statb.st_mode)) continue; stat_fd = open(stat_file, O_RDONLY); if (stat_fd == -1) continue; memset(sbuf, 0, sizeof(sbuf)); num_read = read(stat_fd, sbuf, sizeof(sbuf)); close(stat_fd); if (num_read == -1) continue; if (num_read == sizeof(sbuf)) sbuf[num_read - 1] = '\0'; else sbuf[num_read] = '\0'; /* * Finally match proc name to iscsi thread name. * In newer kernels the name is iscsi_wq_%HOST_NO. * In older kernels before 2.6.30, it was scsi_wq_%HOST_NO. * * We only support newer kernels. */ proc_name = strchr(sbuf, '(') + 1; if (!proc_name) continue; proc_name_end = strchr(proc_name, ')'); if (!proc_name_end) continue; *proc_name_end = '\0'; if (sscanf(proc_name, "iscsi_q_%u\n", &host_no) == 1) { if (host_no == session->hostno) { if (!setpriority(PRIO_PROCESS, pid, session->nrec.session.xmit_thread_priority)) { closedir(proc_dir); return; } else break; } } } closedir(proc_dir); fail: log_error("Could not set session%d priority. " "READ/WRITE throughout and latency could be " "affected.", session->id); } static int session_ipc_create(struct iscsi_session *session) { struct iscsi_conn *conn = &session->conn[0]; int err = 0, pass_ep = 1; uint32_t host_no = -1; if (session->t->template->ep_connect != ktransport_ep_connect) pass_ep = 0; retry_create: err = ipc->create_session(session->t->handle, pass_ep ? conn->transport_ep_handle : 0, session->nrec.session.initial_cmdsn, session->nrec.session.cmds_max, session->nrec.session.queue_depth, &session->id, &host_no); /* * Older kernels were not passed the sessions's leading conn ep, * so we will get -EINVAL || -ENOSYS for iser. * * 2.6.22 and earlier would send -EINVAL instead of -ENOSYS. */ if (pass_ep && (err == -ENOSYS || err == -EINVAL)) { pass_ep = 0; goto retry_create; } if (!err) { session->hostno = host_no; session_increase_wq_priority(session); } return err; } static void setup_offload_login_phase(iscsi_conn_t *conn) { iscsi_session_t *session = conn->session; iscsi_login_context_t *c = &conn->login_context; int rc; actor_delete(&conn->login_timer); if (iscsi_session_set_params(conn)) { iscsi_login_eh(conn, c->qtask, ISCSI_ERR_LOGIN); return; } if (iscsi_session_set_neg_params(conn)) { iscsi_login_eh(conn, c->qtask, ISCSI_ERR_LOGIN); return; } if (iscsi_host_set_params(session)) { iscsi_login_eh(conn, c->qtask, ISCSI_ERR_LOGIN); return; } conn->state = ISCSI_CONN_STATE_IN_LOGIN; if (ipc->start_conn(session->t->handle, session->id, conn->id, &rc) || rc) { if (rc == -EEXIST) { log_error("Session already exists."); session_conn_shutdown(conn, c->qtask, ISCSI_ERR_SESS_EXISTS); } else { log_error("can't start connection %d:%d retcode (%d)", session->id, conn->id, rc); iscsi_login_eh(conn, c->qtask, ISCSI_ERR_INTERNAL); } return; } session->notify_qtask = c->qtask; } static void session_conn_poll(void *data) { struct iscsi_ev_context *ev_context = data; iscsi_conn_t *conn = ev_context->conn; struct iscsi_session *session = conn->session; int err = ISCSI_SUCCESS; queue_task_t *qtask = ev_context->data; iscsi_login_context_t *c = &conn->login_context; int rc; iscsi_ev_context_put(ev_context); if (conn->state != ISCSI_CONN_STATE_XPT_WAIT) return; rc = session->t->template->ep_poll(conn, 1); if (rc == 0) { log_debug(4, "poll not connected %d", rc); ev_context = iscsi_ev_context_get(conn, 0); if (!ev_context) { /* while polling the recv pool should be full */ log_error("BUG: session_conn_poll could not get conn " "context."); iscsi_login_eh(conn, qtask, ISCSI_ERR_INTERNAL); return; } ev_context->data = qtask; /* not connected yet, check later */ iscsi_sched_ev_context(ev_context, conn, 1, EV_CONN_POLL); } else if (rc > 0) { /* connected! */ memset(c, 0, sizeof(iscsi_login_context_t)); /* do not allocate new connection in case of reopen */ if (session->id == INVALID_SESSION_ID) { if (conn->id == 0 && session_ipc_create(session)) { log_error("Can't create session."); err = ISCSI_ERR_INTERNAL; goto cleanup; } log_debug(3, "created new iSCSI session sid %d host " "no %u", session->id, session->hostno); err = ipc->create_conn(session->t->handle, session->id, conn->id, &conn->id); if (err) { log_error("Can't create connection."); err = ISCSI_ERR_INTERNAL; goto cleanup; } log_debug(3, "created new iSCSI connection " "%d:%d", session->id, conn->id); } iscsi_copy_operational_params(conn, &session->nrec.session.iscsi, &session->nrec.conn[conn->id].iscsi); /* * TODO: use the iface number or some other value * so this will be persistent */ session->isid[3] = (session->id >> 16) & 0xff; session->isid[4] = (session->id >> 8) & 0xff; session->isid[5] = session->id & 0xff; if (ipc->bind_conn(session->t->handle, session->id, conn->id, conn->transport_ep_handle, (conn->id == 0), &rc) || rc) { log_error("can't bind conn %d:%d to session %d, " "retcode %d (%d)", session->id, conn->id, session->id, rc, errno); iscsi_login_eh(conn, qtask, ISCSI_ERR_LOGIN); return; } log_debug(3, "bound iSCSI connection %d:%d to session %d", session->id, conn->id, session->id); c->qtask = qtask; c->cid = conn->id; c->buffer = conn->data; c->bufsize = sizeof(conn->data); conn->exp_statsn = iscsi_sysfs_get_exp_statsn(session->id); if (session->t->caps & CAP_LOGIN_OFFLOAD) { setup_offload_login_phase(conn); return; } if (iscsi_session_set_params(conn)) { iscsi_login_eh(conn, qtask, ISCSI_ERR_LOGIN); return; } if (iscsi_host_set_params(session)) { iscsi_login_eh(conn, qtask, ISCSI_ERR_LOGIN); return; } if (iscsi_login_begin(session, c)) { iscsi_login_eh(conn, qtask, ISCSI_ERR_LOGIN); return; } conn->state = ISCSI_CONN_STATE_IN_LOGIN; if (iscsi_login_req(session, c)) { iscsi_login_eh(conn, qtask, ISCSI_ERR_LOGIN); return; } } else { log_debug(4, "poll error %d", rc); queue_delayed_reopen(qtask, ISCSI_CONN_ERR_REOPEN_DELAY); } return; cleanup: session_conn_shutdown(conn, qtask, err); } static void session_conn_process_login(void *data) { struct iscsi_ev_context *ev_context = data; enum iscsi_conn_state state = *(enum iscsi_conn_state *) ev_context->data; struct iscsi_conn *conn = ev_context->conn; struct iscsi_session *session = conn->session; iscsi_login_context_t *c = &conn->login_context; queue_task_t *qtask; iscsi_ev_context_put(ev_context); if (!(session->t->caps & CAP_LOGIN_OFFLOAD)) return; if (state == ISCSI_CONN_STATE_FREE) goto failed_login; if (conn->state == ISCSI_CONN_STATE_LOGGED_IN) return; conn->state = ISCSI_CONN_STATE_LOGGED_IN; /* * ok we were in_login and now we got the notification that we are * logged in */ log_debug(3, "session created sid %u host no %d", session->id, session->hostno); if (session->r_stage == R_STAGE_NO_CHANGE || session->r_stage == R_STAGE_SESSION_REDIRECT) { /* * scan host is one-time deal. We * don't want to re-scan it on recovery. */ session_scan_host(session, session->hostno, c->qtask); session->notify_qtask = NULL; log_warning("Connection%d:%d to [target: %s, portal: %s,%d] " "through [iface: %s] is operational now", session->id, conn->id, session->nrec.name, session->nrec.conn[conn->id].address, session->nrec.conn[conn->id].port, session->nrec.iface.name); } else { session->notify_qtask = NULL; mgmt_ipc_write_rsp(c->qtask, ISCSI_SUCCESS); } /* * reset ERL=0 reopen counter */ session->reopen_cnt = 0; session->r_stage = R_STAGE_NO_CHANGE; return; failed_login: qtask = session->notify_qtask; session->notify_qtask = NULL; mgmt_ipc_write_rsp(qtask, ISCSI_ERR_LOGIN); if (ipc->destroy_conn(session->t->handle, session->id, conn->id)) log_error("can not safely destroy connection %d", conn->id); if (ipc->destroy_session(session->t->handle, session->id)) log_error("can not safely destroy session %d", session->id); __session_destroy(session); } static int iscsi_sched_ev_context(struct iscsi_ev_context *ev_context, struct iscsi_conn *conn, unsigned long tmo, int event) { enum iscsi_err error; log_debug(7, "sched conn context %p event %d, tmo %lu", &ev_context->actor, event, tmo); ev_context->conn = conn; switch (event) { case EV_CONN_RECV_PDU: actor_init(&ev_context->actor, session_conn_recv_pdu, ev_context); actor_schedule(&ev_context->actor); break; case EV_CONN_ERROR: error = *(enum iscsi_err *)ev_context->data; actor_init(&ev_context->actor, session_conn_error, ev_context); /* * We handle invalid host, by killing the session. * It must go at the head of the queue, so we do not * initiate error handling or logout or some other op. */ if (error == ISCSI_ERR_INVALID_HOST) actor_schedule_head(&ev_context->actor); else actor_schedule(&ev_context->actor); break; case EV_CONN_LOGIN: actor_init(&ev_context->actor, session_conn_process_login, ev_context); actor_schedule(&ev_context->actor); break; case EV_CONN_POLL: actor_timer(&ev_context->actor, tmo, session_conn_poll, ev_context); break; case EV_CONN_LOGOUT_TIMER: actor_timer(&ev_context->actor, tmo, iscsi_logout_timedout, ev_context); break; case EV_CONN_STOP: actor_init(&ev_context->actor, iscsi_stop, ev_context); actor_schedule(&ev_context->actor); break; default: log_error("Invalid event type %d.", event); } return 0; } static iscsi_session_t* session_find_by_rec(node_rec_t *rec) { struct iscsi_transport *t; iscsi_session_t *session; list_for_each_entry(t, &transports, list) { list_for_each_entry(session, &t->sessions, list) { if (__iscsi_match_session(rec, session->nrec.name, session->nrec.conn[0].address, session->nrec.conn[0].port, &session->nrec.iface, MATCH_ANY_SID)) return session; } } return NULL; } /* * a session could be running in the kernel but not in iscsid * due to a resync or because some other app started the session */ static int session_is_running(node_rec_t *rec) { int nr_found = 0; if (session_find_by_rec(rec)) return 1; if (iscsi_sysfs_for_each_session(rec, &nr_found, iscsi_match_session, 0)) return 1; return 0; } static int __session_login_task(node_rec_t *rec, queue_task_t *qtask) { iscsi_session_t *session; iscsi_conn_t *conn; struct iscsi_transport *t; int rc; if (session_is_running(rec)) { if (rec->session.multiple) log_debug(2, "Adding a copy of an existing session"); else return ISCSI_ERR_SESS_EXISTS; } t = iscsi_sysfs_get_transport_by_name(rec->iface.transport_name); if (!t) return ISCSI_ERR_TRANS_NOT_FOUND; if ((!(t->caps & CAP_RECOVERY_L0) && rec->session.iscsi.ERL != 0) || (!(t->caps & CAP_RECOVERY_L1) && rec->session.iscsi.ERL > 1)) { log_error("Transport '%s' does not support ERL %d." "Setting ERL to ERL0.", t->name, rec->session.iscsi.ERL); rec->session.iscsi.ERL = 0; } if (!(t->caps & CAP_MULTI_R2T) && rec->session.iscsi.MaxOutstandingR2T) { log_error("Transport '%s' does not support " "MaxOutstandingR2T %d. Setting " "MaxOutstandingR2T to 1.", t->name, rec->session.iscsi.MaxOutstandingR2T); rec->session.iscsi.MaxOutstandingR2T = 1; } if (!(t->caps & CAP_HDRDGST) && rec->conn[0].iscsi.HeaderDigest) { log_error("Transport '%s' does not support " "HeaderDigest != None. Setting HeaderDigest " "to None.", t->name); rec->conn[0].iscsi.HeaderDigest = CONFIG_DIGEST_NEVER; } if (!(t->caps & CAP_DATADGST) && rec->conn[0].iscsi.DataDigest) { log_error("Transport '%s' does not support " "DataDigest != None. Setting DataDigest " "to None", t->name); rec->conn[0].iscsi.DataDigest = CONFIG_DIGEST_NEVER; } if (!(t->caps & CAP_MARKERS) && rec->conn[0].iscsi.IFMarker) { log_error("Transport '%s' does not support IFMarker. " "Disabling IFMarkers.", t->name); rec->conn[0].iscsi.IFMarker = 0; } if (!(t->caps & CAP_MARKERS) && rec->conn[0].iscsi.OFMarker) { log_error("Transport '%s' does not support OFMarker." "Disabling OFMarkers.", t->name); rec->conn[0].iscsi.OFMarker = 0; } session = __session_create(rec, t, &rc); if (rc == ISCSI_ERR_HOST_NOT_FOUND) return rc; else if (!session) return ISCSI_ERR_LOGIN; /* FIXME: login all connections! marked as "automatic" */ /* create leading connection */ rc = __session_conn_create(session, 0); if (rc) { __session_destroy(session); return rc; } conn = &session->conn[0]; qtask->conn = conn; rc = iscsi_host_set_net_params(&rec->iface, session); if (rc == ISCSI_ERR_AGAIN) { /* * host/iscsiuio not ready. Cannot block iscsid, so caller is * going to internally retry the operation. */ __session_destroy(session); return ISCSI_ERR_HOST_NOT_FOUND; } else if (rc) { __session_destroy(session); return ISCSI_ERR_LOGIN; } if (gettimeofday(&conn->initial_connect_time, NULL)) log_error("Could not get initial connect time. If " "login errors iscsid may give up the initial " "login early. You should manually login."); conn->state = ISCSI_CONN_STATE_XPT_WAIT; qtask->rsp.command = MGMT_IPC_SESSION_LOGIN; qtask->rsp.err = ISCSI_SUCCESS; if (iscsi_conn_connect(conn, qtask)) { log_debug(4, "Initial connect failed. Waiting %u seconds " "before trying to reconnect.", ISCSI_CONN_ERR_REOPEN_DELAY); queue_delayed_reopen(qtask, ISCSI_CONN_ERR_REOPEN_DELAY); } return ISCSI_SUCCESS; } int session_login_task(node_rec_t *rec, queue_task_t *qtask) { int rc; rc = __session_login_task(rec, qtask); if (rc == ISCSI_ERR_HOST_NOT_FOUND) { rc = queue_session_login_task_retry(NULL, rec, qtask); if (rc) return rc; /* * we are going to internally retry. Will return final rc * when completed */ return ISCSI_SUCCESS; } return rc; } static void session_login_task_retry(void *data) { struct login_task_retry_info *info = data; struct node_rec *rec = info->rec; int rc; rc = __session_login_task(rec, info->qtask); if (rc == ISCSI_ERR_HOST_NOT_FOUND) { if (info->retry_count == rec->conn[0].timeo.login_timeout) { /* give up */ goto write_rsp; } rc = queue_session_login_task_retry(info, rec, info->qtask); if (rc) goto write_rsp; /* we are going to internally retry */ return; } else if (rc) { /* hard error - no retry */ goto write_rsp; } else /* successfully started login operation */ goto free; write_rsp: mgmt_ipc_write_rsp(info->qtask, rc); free: free(info); } static int queue_session_login_task_retry(struct login_task_retry_info *info, node_rec_t *rec, queue_task_t *qtask) { if (!info) { info = malloc(sizeof(*info)); if (!info) return ISCSI_ERR_NOMEM; memset(info, 0, sizeof(*info)); info->qtask = qtask; info->rec = rec; } info->retry_count++; log_debug(4, "queue session setup attempt in %d secs, retries %d", 1, info->retry_count); actor_timer(&info->retry_actor, 1, session_login_task_retry, info); return 0; } static int sync_conn(iscsi_session_t *session, uint32_t cid) { iscsi_conn_t *conn; int rc; rc = __session_conn_create(session, cid); if (rc) return rc; conn = &session->conn[cid]; /* TODO: must export via sysfs so we can pick this up */ conn->state = ISCSI_CONN_STATE_CLEANUP_WAIT; return 0; } int iscsi_sync_session(node_rec_t *rec, queue_task_t *qtask, uint32_t sid) { iscsi_session_t *session; struct iscsi_transport *t; int err; t = iscsi_sysfs_get_transport_by_name(rec->iface.transport_name); if (!t) return ISCSI_ERR_TRANS_NOT_FOUND; session = __session_create(rec, t, &err); if (!session) return ISCSI_ERR_LOGIN; session->id = sid; session->hostno = iscsi_sysfs_get_host_no_from_sid(sid, &err); if (err) { log_error("Could not get hostno for session %d", sid); goto destroy_session; } session->r_stage = R_STAGE_SESSION_REOPEN; err = sync_conn(session, 0); if (err) goto destroy_session; qtask->rsp.command = MGMT_IPC_SESSION_SYNC; log_debug(3, "Started sync iSCSI session %d", session->id); session->notify_qtask = qtask; session_conn_reopen(&session->conn[0], qtask, STOP_CONN_RECOVER); return 0; destroy_session: __session_destroy(session); log_error("Could not sync session%d err %d", sid, err); return err; } static int session_unbind(struct iscsi_session *session) { int err; err = ipc->unbind_session(session->t->handle, session->id); if (err) /* older kernels did not support unbind */ log_debug(2, "Could not unbind session %d.", err); return err; } int session_logout_task(int sid, queue_task_t *qtask) { iscsi_session_t *session; iscsi_conn_t *conn; int rc = ISCSI_SUCCESS; session = session_find_by_sid(sid); if (!session) { log_debug(1, "session sid %d not found.", sid); return ISCSI_ERR_SESS_NOT_FOUND; } conn = &session->conn[0]; /* * If syncing up, in XPT_WAIT, and REOPENing, then return * an informative error, since the target for this session * is likely not connected */ if (session->notify_qtask && (conn->state == ISCSI_CONN_STATE_XPT_WAIT) && (session->r_stage == R_STAGE_SESSION_REOPEN)) { log_warning("session cannot be terminted because it's trying to reconnect: try again later"); return ISCSI_ERR_SESSION_NOT_CONNECTED; } /* * If syncing up and not reconnecting, * or if this is the initial login and mgmt_ipc * has not been notified of that result fail the logout request */ if (session->notify_qtask || ((conn->state == ISCSI_CONN_STATE_XPT_WAIT || conn->state == ISCSI_CONN_STATE_IN_LOGIN) && (session->r_stage == R_STAGE_NO_CHANGE || session->r_stage == R_STAGE_SESSION_REDIRECT))) { invalid_state: log_error("session in invalid state for logout. " "Try again later"); return ISCSI_ERR_INTERNAL; } if (dconfig->safe_logout && session_in_use(sid)) { log_error("Session is actively in use for mounted storage, " "and iscsid.safe_logout is configured."); return ISCSI_ERR_BUSY; } /* FIXME: logout all active connections */ conn = &session->conn[0]; if (conn->logout_qtask) goto invalid_state; qtask->conn = conn; qtask->rsp.command = MGMT_IPC_SESSION_LOGOUT; conn->logout_qtask = qtask; switch (conn->state) { case ISCSI_CONN_STATE_LOGGED_IN: if (!session_unbind(session)) return ISCSI_SUCCESS; /* LLDs that offload login also offload logout */ if (!(session->t->caps & CAP_LOGIN_OFFLOAD)) { /* unbind is not supported so just do old logout */ if (!iscsi_send_logout(conn)) return ISCSI_SUCCESS; } log_error("Could not send logout pdu. Dropping session"); /* fallthrough */ default: rc = session_conn_shutdown(conn, qtask, ISCSI_SUCCESS); break; } return rc; } int iscsi_host_send_targets(__attribute__((unused))queue_task_t *qtask, int host_no, __attribute__((unused))int do_login, struct sockaddr_storage *ss) { struct iscsi_transport *t; t = iscsi_sysfs_get_transport_by_hba(host_no); if (!t) { log_error("Invalid host no %d for sendtargets", host_no); return ISCSI_ERR_TRANS_NOT_FOUND; } if (!(t->caps & CAP_SENDTARGETS_OFFLOAD)) return ISCSI_ERR_TRANS_CAPS; if (ipc->sendtargets(t->handle, host_no, (struct sockaddr *)ss)) return ISCSI_ERR; return ISCSI_SUCCESS; } /* * HW drivers like qla4xxx present an interface that hides most of the iscsi * details. Userspace sends down a discovery event then it gets notified * if the sessions that were logged in as a result asynchronously, or * the card will have sessions preset in the FLASH and will log into them * automaotically then send us notification that a session is setup. */ static void iscsi_async_session_creation(uint32_t host_no, uint32_t sid) { struct iscsi_transport *transport; transport = iscsi_sysfs_get_transport_by_hba(host_no); if (!transport) return; if (!(transport->caps & CAP_FW_DB)) return; log_debug(3, "session created sid %u host no %d", sid, host_no); session_online_devs(host_no, sid); session_scan_host(NULL, host_no, NULL); } static void iscsi_async_session_destruction(uint32_t host_no, uint32_t sid) { log_debug(3, "session destroyed sid %u host no %d", sid, host_no); } static struct iscsi_ipc_ev_clbk ipc_clbk = { .create_session = iscsi_async_session_creation, .destroy_session = iscsi_async_session_destruction, .get_ev_context = iscsi_ev_context_get, .put_ev_context = iscsi_ev_context_put, .sched_ev_context = iscsi_sched_ev_context, }; void iscsi_initiator_init(void) { ipc_register_ev_callback(&ipc_clbk); }