491 lines
11 KiB
C
491 lines
11 KiB
C
/* $OpenBSD: session.c,v 1.10 2025/01/16 16:17:32 claudio Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2011 Claudio Jeker <claudio@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/uio.h>
|
|
|
|
#include <scsi/iscsi.h>
|
|
#include <scsi/scsi_all.h>
|
|
#include <dev/vscsivar.h>
|
|
|
|
#include <event.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "iscsid.h"
|
|
#include "log.h"
|
|
|
|
void session_fsm_callback(int, short, void *);
|
|
int sess_do_start(struct session *, struct sessev *);
|
|
int sess_do_conn_loggedin(struct session *, struct sessev *);
|
|
int sess_do_conn_fail(struct session *, struct sessev *);
|
|
int sess_do_conn_closed(struct session *, struct sessev *);
|
|
int sess_do_stop(struct session *, struct sessev *);
|
|
int sess_do_free(struct session *, struct sessev *);
|
|
int sess_do_reinstatement(struct session *, struct sessev *);
|
|
|
|
const char *sess_state(int);
|
|
const char *sess_event(enum s_event);
|
|
|
|
struct session *
|
|
session_find(struct initiator *i, char *name)
|
|
{
|
|
struct session *s;
|
|
|
|
TAILQ_FOREACH(s, &i->sessions, entry) {
|
|
if (strcmp(s->config.SessionName, name) == 0)
|
|
return s;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct session *
|
|
session_new(struct initiator *i, u_int8_t st)
|
|
{
|
|
struct session *s;
|
|
|
|
if (!(s = calloc(1, sizeof(*s))))
|
|
return NULL;
|
|
|
|
/* use the same qualifier unless there is a conflict */
|
|
s->isid_base = i->config.isid_base;
|
|
s->isid_qual = i->config.isid_qual;
|
|
s->cmdseqnum = arc4random();
|
|
s->itt = arc4random();
|
|
s->initiator = i;
|
|
s->state = SESS_INIT;
|
|
|
|
if (st == SESSION_TYPE_DISCOVERY)
|
|
s->target = 0;
|
|
else
|
|
s->target = s->initiator->target++;
|
|
|
|
TAILQ_INSERT_HEAD(&i->sessions, s, entry);
|
|
TAILQ_INIT(&s->connections);
|
|
TAILQ_INIT(&s->tasks);
|
|
|
|
return s;
|
|
}
|
|
|
|
void
|
|
session_cleanup(struct session *s)
|
|
{
|
|
struct connection *c;
|
|
|
|
taskq_cleanup(&s->tasks);
|
|
|
|
while ((c = TAILQ_FIRST(&s->connections)) != NULL)
|
|
conn_free(c);
|
|
|
|
free(s->config.TargetName);
|
|
free(s->config.InitiatorName);
|
|
free(s);
|
|
}
|
|
|
|
int
|
|
session_shutdown(struct session *s)
|
|
{
|
|
log_debug("session[%s] going down", s->config.SessionName);
|
|
|
|
s->action = SESS_ACT_DOWN;
|
|
if (s->state & (SESS_INIT | SESS_FREE)) {
|
|
/* no active session, so do a quick cleanup */
|
|
struct connection *c;
|
|
while ((c = TAILQ_FIRST(&s->connections)) != NULL)
|
|
conn_free(c);
|
|
return 0;
|
|
}
|
|
|
|
/* cleanup task queue and issue a logout */
|
|
taskq_cleanup(&s->tasks);
|
|
initiator_logout(s, NULL, ISCSI_LOGOUT_CLOSE_SESS);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
session_config(struct session *s, struct session_config *sc)
|
|
{
|
|
free(s->config.TargetName);
|
|
s->config.TargetName = NULL;
|
|
free(s->config.InitiatorName);
|
|
s->config.InitiatorName = NULL;
|
|
|
|
s->config = *sc;
|
|
|
|
if (sc->TargetName) {
|
|
s->config.TargetName = strdup(sc->TargetName);
|
|
if (s->config.TargetName == NULL)
|
|
fatal("strdup");
|
|
}
|
|
if (sc->InitiatorName) {
|
|
s->config.InitiatorName = strdup(sc->InitiatorName);
|
|
if (s->config.InitiatorName == NULL)
|
|
fatal("strdup");
|
|
} else
|
|
s->config.InitiatorName = default_initiator_name();
|
|
}
|
|
|
|
void
|
|
session_task_issue(struct session *s, struct task *t)
|
|
{
|
|
TAILQ_INSERT_TAIL(&s->tasks, t, entry);
|
|
session_schedule(s);
|
|
}
|
|
|
|
void
|
|
session_logout_issue(struct session *s, struct task *t)
|
|
{
|
|
struct connection *c, *rc = NULL;
|
|
|
|
/* find first free session or first available session */
|
|
TAILQ_FOREACH(c, &s->connections, entry) {
|
|
if (conn_task_ready(c)) {
|
|
conn_fsm(c, CONN_EV_LOGOUT);
|
|
conn_task_issue(c, t);
|
|
return;
|
|
}
|
|
if (c->state & CONN_RUNNING)
|
|
rc = c;
|
|
}
|
|
|
|
if (rc) {
|
|
conn_fsm(rc, CONN_EV_LOGOUT);
|
|
conn_task_issue(rc, t);
|
|
return;
|
|
}
|
|
|
|
/* XXX must open new connection, gulp */
|
|
fatalx("session_logout_issue needs more work");
|
|
}
|
|
|
|
void
|
|
session_schedule(struct session *s)
|
|
{
|
|
struct task *t = TAILQ_FIRST(&s->tasks);
|
|
struct connection *c;
|
|
|
|
if (!t)
|
|
return;
|
|
|
|
/* XXX IMMEDIATE TASK NEED SPECIAL HANDLING !!!! */
|
|
|
|
/* wake up a idle connection or a not busy one */
|
|
/* XXX this needs more work as it makes the daemon go wrooOOOMM */
|
|
TAILQ_FOREACH(c, &s->connections, entry)
|
|
if (conn_task_ready(c)) {
|
|
TAILQ_REMOVE(&s->tasks, t, entry);
|
|
conn_task_issue(c, t);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The session FSM runs from a callback so that the connection FSM can finish.
|
|
*/
|
|
void
|
|
session_fsm(struct session *s, enum s_event ev, struct connection *c,
|
|
unsigned int timeout)
|
|
{
|
|
struct timeval tv;
|
|
struct sessev *sev;
|
|
|
|
log_debug("session_fsm[%s]: %s ev %s timeout %d",
|
|
s->config.SessionName, sess_state(s->state),
|
|
sess_event(ev), timeout);
|
|
|
|
if ((sev = malloc(sizeof(*sev))) == NULL)
|
|
fatal("session_fsm");
|
|
sev->conn = c;
|
|
sev->sess = s;
|
|
sev->event = ev;
|
|
|
|
timerclear(&tv);
|
|
tv.tv_sec = timeout;
|
|
if (event_once(-1, EV_TIMEOUT, session_fsm_callback, sev, &tv) == -1)
|
|
fatal("session_fsm");
|
|
}
|
|
|
|
struct {
|
|
int state;
|
|
enum s_event event;
|
|
int (*action)(struct session *, struct sessev *);
|
|
} s_fsm[] = {
|
|
{ SESS_INIT, SESS_EV_START, sess_do_start },
|
|
{ SESS_FREE, SESS_EV_START, sess_do_start },
|
|
{ SESS_FREE, SESS_EV_CONN_LOGGED_IN, sess_do_conn_loggedin }, /* N1 */
|
|
{ SESS_FREE, SESS_EV_CLOSED, sess_do_stop },
|
|
{ SESS_LOGGED_IN, SESS_EV_CONN_LOGGED_IN, sess_do_conn_loggedin },
|
|
{ SESS_RUNNING, SESS_EV_CONN_CLOSED, sess_do_conn_closed }, /* N3 */
|
|
{ SESS_RUNNING, SESS_EV_CONN_FAIL, sess_do_conn_fail }, /* N5 */
|
|
{ SESS_RUNNING, SESS_EV_CLOSED, sess_do_free }, /* XXX */
|
|
{ SESS_FAILED, SESS_EV_START, sess_do_start },
|
|
{ SESS_FAILED, SESS_EV_TIMEOUT, sess_do_free }, /* N6 */
|
|
{ SESS_FAILED, SESS_EV_FREE, sess_do_free }, /* N6 */
|
|
{ SESS_FAILED, SESS_EV_CONN_LOGGED_IN, sess_do_reinstatement }, /* N4 */
|
|
{ 0, 0, NULL }
|
|
};
|
|
|
|
void
|
|
session_fsm_callback(int fd, short event, void *arg)
|
|
{
|
|
struct sessev *sev = arg;
|
|
struct session *s = sev->sess;
|
|
int i, ns;
|
|
|
|
for (i = 0; s_fsm[i].action != NULL; i++) {
|
|
if (s->state & s_fsm[i].state &&
|
|
sev->event == s_fsm[i].event) {
|
|
log_debug("sess_fsm[%s]: %s ev %s",
|
|
s->config.SessionName, sess_state(s->state),
|
|
sess_event(sev->event));
|
|
ns = s_fsm[i].action(s, sev);
|
|
if (ns == -1)
|
|
/* XXX better please */
|
|
fatalx("sess_fsm: action failed");
|
|
log_debug("sess_fsm[%s]: new state %s",
|
|
s->config.SessionName,
|
|
sess_state(ns));
|
|
s->state = ns;
|
|
break;
|
|
}
|
|
}
|
|
if (s_fsm[i].action == NULL) {
|
|
log_warnx("sess_fsm[%s]: unhandled state transition "
|
|
"[%s, %s]", s->config.SessionName,
|
|
sess_state(s->state), sess_event(sev->event));
|
|
fatalx("bjork bjork bjork");
|
|
}
|
|
free(sev);
|
|
log_debug("sess_fsm: done");
|
|
}
|
|
|
|
int
|
|
sess_do_start(struct session *s, struct sessev *sev)
|
|
{
|
|
log_debug("new connection to %s",
|
|
log_sockaddr(&s->config.connection.TargetAddr));
|
|
|
|
/* initialize the session params */
|
|
s->mine = initiator_sess_defaults;
|
|
s->his = iscsi_sess_defaults;
|
|
s->active = iscsi_sess_defaults;
|
|
|
|
if (s->config.SessionType != SESSION_TYPE_DISCOVERY &&
|
|
s->config.MaxConnections)
|
|
s->mine.MaxConnections = s->config.MaxConnections;
|
|
|
|
conn_new(s, &s->config.connection);
|
|
|
|
/* XXX kill SESS_FREE it seems to be bad */
|
|
if (s->state == SESS_INIT)
|
|
return SESS_FREE;
|
|
else
|
|
return s->state;
|
|
}
|
|
|
|
int
|
|
sess_do_conn_loggedin(struct session *s, struct sessev *sev)
|
|
{
|
|
if (s->state & SESS_LOGGED_IN)
|
|
return SESS_LOGGED_IN;
|
|
|
|
if (s->config.SessionType == SESSION_TYPE_DISCOVERY) {
|
|
initiator_discovery(s);
|
|
return SESS_LOGGED_IN;
|
|
}
|
|
|
|
iscsi_merge_sess_params(&s->active, &s->mine, &s->his);
|
|
vscsi_event(VSCSI_REQPROBE, s->target, -1);
|
|
s->holdTimer = 0;
|
|
|
|
return SESS_LOGGED_IN;
|
|
}
|
|
|
|
int
|
|
sess_do_conn_fail(struct session *s, struct sessev *sev)
|
|
{
|
|
struct connection *c = sev->conn;
|
|
int state = SESS_FREE;
|
|
|
|
if (sev->conn == NULL) {
|
|
log_warnx("Just what do you think you're doing, Dave?");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* cleanup connections:
|
|
* Connections in state FREE can be removed.
|
|
* Connections in any error state will cause the session to enter
|
|
* the FAILED state. If no sessions are left and the session was
|
|
* not already FREE then implicit recovery needs to be done.
|
|
*/
|
|
|
|
switch (c->state) {
|
|
case CONN_FREE:
|
|
conn_free(c);
|
|
break;
|
|
case CONN_CLEANUP_WAIT:
|
|
break;
|
|
default:
|
|
log_warnx("It can only be attributable to human error.");
|
|
return -1;
|
|
}
|
|
|
|
TAILQ_FOREACH(c, &s->connections, entry) {
|
|
if (c->state & CONN_FAILED) {
|
|
state = SESS_FAILED;
|
|
conn_fsm(c, CONN_EV_CLEANING_UP);
|
|
} else if (c->state & CONN_RUNNING && state != SESS_FAILED)
|
|
state = SESS_LOGGED_IN;
|
|
}
|
|
|
|
session_fsm(s, SESS_EV_START, NULL, s->holdTimer);
|
|
/* exponential back-off on constant failure */
|
|
if (s->holdTimer < ISCSID_HOLD_TIME_MAX)
|
|
s->holdTimer = s->holdTimer ? s->holdTimer * 2 : 1;
|
|
|
|
return state;
|
|
}
|
|
|
|
int
|
|
sess_do_conn_closed(struct session *s, struct sessev *sev)
|
|
{
|
|
struct connection *c = sev->conn;
|
|
int state = SESS_FREE;
|
|
|
|
if (c == NULL || c->state != CONN_FREE) {
|
|
log_warnx("Just what do you think you're doing, Dave?");
|
|
return -1;
|
|
}
|
|
conn_free(c);
|
|
|
|
TAILQ_FOREACH(c, &s->connections, entry) {
|
|
if (c->state & CONN_FAILED) {
|
|
state = SESS_FAILED;
|
|
break;
|
|
} else if (c->state & CONN_RUNNING)
|
|
state = SESS_LOGGED_IN;
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
int
|
|
sess_do_stop(struct session *s, struct sessev *sev)
|
|
{
|
|
struct connection *c;
|
|
|
|
/* XXX do graceful closing of session and go to INIT state at the end */
|
|
|
|
while ((c = TAILQ_FIRST(&s->connections)) != NULL)
|
|
conn_free(c);
|
|
|
|
/* XXX anything else to reset to initial state? */
|
|
return SESS_INIT;
|
|
}
|
|
|
|
int
|
|
sess_do_free(struct session *s, struct sessev *sev)
|
|
{
|
|
struct connection *c;
|
|
|
|
while ((c = TAILQ_FIRST(&s->connections)) != NULL)
|
|
conn_free(c);
|
|
|
|
return SESS_FREE;
|
|
}
|
|
|
|
const char *conn_state(int);
|
|
|
|
|
|
int
|
|
sess_do_reinstatement(struct session *s, struct sessev *sev)
|
|
{
|
|
struct connection *c, *nc;
|
|
|
|
TAILQ_FOREACH_SAFE(c, &s->connections, entry, nc) {
|
|
log_debug("sess reinstatement[%s]: %s",
|
|
s->config.SessionName, conn_state(c->state));
|
|
|
|
if (c->state & CONN_FAILED) {
|
|
conn_fsm(c, CONN_EV_FREE);
|
|
conn_free(c);
|
|
}
|
|
}
|
|
|
|
return SESS_LOGGED_IN;
|
|
}
|
|
|
|
const char *
|
|
sess_state(int s)
|
|
{
|
|
static char buf[15];
|
|
|
|
switch (s) {
|
|
case SESS_INIT:
|
|
return "INIT";
|
|
case SESS_FREE:
|
|
return "FREE";
|
|
case SESS_LOGGED_IN:
|
|
return "LOGGED_IN";
|
|
case SESS_FAILED:
|
|
return "FAILED";
|
|
default:
|
|
snprintf(buf, sizeof(buf), "UKNWN %x", s);
|
|
return buf;
|
|
}
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
const char *
|
|
sess_event(enum s_event e)
|
|
{
|
|
static char buf[15];
|
|
|
|
switch (e) {
|
|
case SESS_EV_START:
|
|
return "start";
|
|
case SESS_EV_STOP:
|
|
return "stop";
|
|
case SESS_EV_CONN_LOGGED_IN:
|
|
return "connection logged in";
|
|
case SESS_EV_CONN_FAIL:
|
|
return "connection fail";
|
|
case SESS_EV_CONN_CLOSED:
|
|
return "connection closed";
|
|
case SESS_EV_REINSTATEMENT:
|
|
return "connection reinstated";
|
|
case SESS_EV_CLOSED:
|
|
return "session closed";
|
|
case SESS_EV_TIMEOUT:
|
|
return "timeout";
|
|
case SESS_EV_FREE:
|
|
return "free";
|
|
case SESS_EV_FAIL:
|
|
return "fail";
|
|
}
|
|
|
|
snprintf(buf, sizeof(buf), "UKNWN %d", e);
|
|
return buf;
|
|
}
|