/* $OpenBSD: session.c,v 1.7 2014/05/10 11:30:47 claudio Exp $ */ /* * Copyright (c) 2011 Claudio Jeker * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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) { if (s->config.TargetName) free(s->config.TargetName); s->config.TargetName = NULL; if (s->config.InitiatorName) 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 } }; /* ARGSUSED */ 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); TAILQ_REMOVE(&s->connections, c, entry); 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; }