/*
* 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 <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#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; i<ISCSI_CONN_MAX; i++) {
if (session->conn[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; i<ISCSI_CONN_MAX; i++) {
if (session->conn[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);
}