session.c revision 1.7
1/* $OpenBSD: session.c,v 1.7 2014/05/10 11:30:47 claudio Exp $ */ 2 3/* 4 * Copyright (c) 2011 Claudio Jeker <claudio@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19#include <sys/types.h> 20#include <sys/ioctl.h> 21#include <sys/queue.h> 22#include <sys/socket.h> 23#include <sys/uio.h> 24 25#include <scsi/iscsi.h> 26#include <scsi/scsi_all.h> 27#include <dev/vscsivar.h> 28 29#include <event.h> 30#include <stdio.h> 31#include <stdlib.h> 32#include <string.h> 33#include <unistd.h> 34 35#include "iscsid.h" 36#include "log.h" 37 38void session_fsm_callback(int, short, void *); 39int sess_do_start(struct session *, struct sessev *); 40int sess_do_conn_loggedin(struct session *, struct sessev *); 41int sess_do_conn_fail(struct session *, struct sessev *); 42int sess_do_conn_closed(struct session *, struct sessev *); 43int sess_do_stop(struct session *, struct sessev *); 44int sess_do_free(struct session *, struct sessev *); 45int sess_do_reinstatement(struct session *, struct sessev *); 46 47const char *sess_state(int); 48const char *sess_event(enum s_event); 49 50struct session * 51session_find(struct initiator *i, char *name) 52{ 53 struct session *s; 54 55 TAILQ_FOREACH(s, &i->sessions, entry) { 56 if (strcmp(s->config.SessionName, name) == 0) 57 return s; 58 } 59 return NULL; 60} 61 62struct session * 63session_new(struct initiator *i, u_int8_t st) 64{ 65 struct session *s; 66 67 if (!(s = calloc(1, sizeof(*s)))) 68 return NULL; 69 70 /* use the same qualifier unless there is a conflict */ 71 s->isid_base = i->config.isid_base; 72 s->isid_qual = i->config.isid_qual; 73 s->cmdseqnum = arc4random(); 74 s->itt = arc4random(); 75 s->initiator = i; 76 s->state = SESS_INIT; 77 78 if (st == SESSION_TYPE_DISCOVERY) 79 s->target = 0; 80 else 81 s->target = s->initiator->target++; 82 83 TAILQ_INSERT_HEAD(&i->sessions, s, entry); 84 TAILQ_INIT(&s->connections); 85 TAILQ_INIT(&s->tasks); 86 87 return s; 88} 89 90void 91session_cleanup(struct session *s) 92{ 93 struct connection *c; 94 95 taskq_cleanup(&s->tasks); 96 97 while ((c = TAILQ_FIRST(&s->connections)) != NULL) 98 conn_free(c); 99 100 free(s->config.TargetName); 101 free(s->config.InitiatorName); 102 free(s); 103} 104 105int 106session_shutdown(struct session *s) 107{ 108 log_debug("session[%s] going down", s->config.SessionName); 109 110 s->action = SESS_ACT_DOWN; 111 if (s->state & (SESS_INIT | SESS_FREE)) { 112 /* no active session, so do a quick cleanup */ 113 struct connection *c; 114 while ((c = TAILQ_FIRST(&s->connections)) != NULL) 115 conn_free(c); 116 return 0; 117 } 118 119 /* cleanup task queue and issue a logout */ 120 taskq_cleanup(&s->tasks); 121 initiator_logout(s, NULL, ISCSI_LOGOUT_CLOSE_SESS); 122 123 return 1; 124} 125 126void 127session_config(struct session *s, struct session_config *sc) 128{ 129 if (s->config.TargetName) 130 free(s->config.TargetName); 131 s->config.TargetName = NULL; 132 if (s->config.InitiatorName) 133 free(s->config.InitiatorName); 134 s->config.InitiatorName = NULL; 135 136 s->config = *sc; 137 138 if (sc->TargetName) { 139 s->config.TargetName = strdup(sc->TargetName); 140 if (s->config.TargetName == NULL) 141 fatal("strdup"); 142 } 143 if (sc->InitiatorName) { 144 s->config.InitiatorName = strdup(sc->InitiatorName); 145 if (s->config.InitiatorName == NULL) 146 fatal("strdup"); 147 } else 148 s->config.InitiatorName = default_initiator_name(); 149} 150 151void 152session_task_issue(struct session *s, struct task *t) 153{ 154 TAILQ_INSERT_TAIL(&s->tasks, t, entry); 155 session_schedule(s); 156} 157 158void 159session_logout_issue(struct session *s, struct task *t) 160{ 161 struct connection *c, *rc = NULL; 162 163 /* find first free session or first available session */ 164 TAILQ_FOREACH(c, &s->connections, entry) { 165 if (conn_task_ready(c)) { 166 conn_fsm(c, CONN_EV_LOGOUT); 167 conn_task_issue(c, t); 168 return; 169 } 170 if (c->state & CONN_RUNNING) 171 rc = c; 172 } 173 174 if (rc) { 175 conn_fsm(rc, CONN_EV_LOGOUT); 176 conn_task_issue(rc, t); 177 return; 178 } 179 180 /* XXX must open new connection, gulp */ 181 fatalx("session_logout_issue needs more work"); 182} 183 184void 185session_schedule(struct session *s) 186{ 187 struct task *t = TAILQ_FIRST(&s->tasks); 188 struct connection *c; 189 190 if (!t) 191 return; 192 193 /* XXX IMMEDIATE TASK NEED SPECIAL HANDLING !!!! */ 194 195 /* wake up a idle connection or a not busy one */ 196 /* XXX this needs more work as it makes the daemon go wrooOOOMM */ 197 TAILQ_FOREACH(c, &s->connections, entry) 198 if (conn_task_ready(c)) { 199 TAILQ_REMOVE(&s->tasks, t, entry); 200 conn_task_issue(c, t); 201 return; 202 } 203} 204 205/* 206 * The session FSM runs from a callback so that the connection FSM can finish. 207 */ 208void 209session_fsm(struct session *s, enum s_event ev, struct connection *c, 210 unsigned int timeout) 211{ 212 struct timeval tv; 213 struct sessev *sev; 214 215 log_debug("session_fsm[%s]: %s ev %s timeout %d", 216 s->config.SessionName, sess_state(s->state), 217 sess_event(ev), timeout); 218 219 if ((sev = malloc(sizeof(*sev))) == NULL) 220 fatal("session_fsm"); 221 sev->conn = c; 222 sev->sess = s; 223 sev->event = ev; 224 225 timerclear(&tv); 226 tv.tv_sec = timeout; 227 if (event_once(-1, EV_TIMEOUT, session_fsm_callback, sev, &tv) == -1) 228 fatal("session_fsm"); 229} 230 231struct { 232 int state; 233 enum s_event event; 234 int (*action)(struct session *, struct sessev *); 235} s_fsm[] = { 236 { SESS_INIT, SESS_EV_START, sess_do_start }, 237 { SESS_FREE, SESS_EV_START, sess_do_start }, 238 { SESS_FREE, SESS_EV_CONN_LOGGED_IN, sess_do_conn_loggedin }, /* N1 */ 239 { SESS_FREE, SESS_EV_CLOSED, sess_do_stop }, 240 { SESS_LOGGED_IN, SESS_EV_CONN_LOGGED_IN, sess_do_conn_loggedin }, 241 { SESS_RUNNING, SESS_EV_CONN_CLOSED, sess_do_conn_closed }, /* N3 */ 242 { SESS_RUNNING, SESS_EV_CONN_FAIL, sess_do_conn_fail }, /* N5 */ 243 { SESS_RUNNING, SESS_EV_CLOSED, sess_do_free }, /* XXX */ 244 { SESS_FAILED, SESS_EV_START, sess_do_start }, 245 { SESS_FAILED, SESS_EV_TIMEOUT, sess_do_free }, /* N6 */ 246 { SESS_FAILED, SESS_EV_FREE, sess_do_free }, /* N6 */ 247 { SESS_FAILED, SESS_EV_CONN_LOGGED_IN, sess_do_reinstatement }, /* N4 */ 248 { 0, 0, NULL } 249}; 250 251/* ARGSUSED */ 252void 253session_fsm_callback(int fd, short event, void *arg) 254{ 255 struct sessev *sev = arg; 256 struct session *s = sev->sess; 257 int i, ns; 258 259 for (i = 0; s_fsm[i].action != NULL; i++) { 260 if (s->state & s_fsm[i].state && 261 sev->event == s_fsm[i].event) { 262 log_debug("sess_fsm[%s]: %s ev %s", 263 s->config.SessionName, sess_state(s->state), 264 sess_event(sev->event)); 265 ns = s_fsm[i].action(s, sev); 266 if (ns == -1) 267 /* XXX better please */ 268 fatalx("sess_fsm: action failed"); 269 log_debug("sess_fsm[%s]: new state %s", 270 s->config.SessionName, 271 sess_state(ns)); 272 s->state = ns; 273 break; 274 } 275 } 276 if (s_fsm[i].action == NULL) { 277 log_warnx("sess_fsm[%s]: unhandled state transition " 278 "[%s, %s]", s->config.SessionName, 279 sess_state(s->state), sess_event(sev->event)); 280 fatalx("bjork bjork bjork"); 281 } 282 free(sev); 283log_debug("sess_fsm: done"); 284} 285 286int 287sess_do_start(struct session *s, struct sessev *sev) 288{ 289 log_debug("new connection to %s", 290 log_sockaddr(&s->config.connection.TargetAddr)); 291 292 /* initialize the session params */ 293 s->mine = initiator_sess_defaults; 294 s->his = iscsi_sess_defaults; 295 s->active = iscsi_sess_defaults; 296 297 if (s->config.SessionType != SESSION_TYPE_DISCOVERY && 298 s->config.MaxConnections) 299 s->mine.MaxConnections = s->config.MaxConnections; 300 301 conn_new(s, &s->config.connection); 302 303 /* XXX kill SESS_FREE it seems to be bad */ 304 if (s->state == SESS_INIT) 305 return SESS_FREE; 306 else 307 return s->state; 308} 309 310int 311sess_do_conn_loggedin(struct session *s, struct sessev *sev) 312{ 313 if (s->state & SESS_LOGGED_IN) 314 return SESS_LOGGED_IN; 315 316 if (s->config.SessionType == SESSION_TYPE_DISCOVERY) { 317 initiator_discovery(s); 318 return SESS_LOGGED_IN; 319 } 320 321 iscsi_merge_sess_params(&s->active, &s->mine, &s->his); 322 vscsi_event(VSCSI_REQPROBE, s->target, -1); 323 s->holdTimer = 0; 324 325 return SESS_LOGGED_IN; 326} 327 328int 329sess_do_conn_fail(struct session *s, struct sessev *sev) 330{ 331 struct connection *c = sev->conn; 332 int state = SESS_FREE; 333 334 if (sev->conn == NULL) { 335 log_warnx("Just what do you think you're doing, Dave?"); 336 return -1; 337 } 338 339 /* 340 * cleanup connections: 341 * Connections in state FREE can be removed. 342 * Connections in any error state will cause the session to enter 343 * the FAILED state. If no sessions are left and the session was 344 * not already FREE then implicit recovery needs to be done. 345 */ 346 347 switch (c->state) { 348 case CONN_FREE: 349 conn_free(c); 350 break; 351 case CONN_CLEANUP_WAIT: 352 break; 353 default: 354 log_warnx("It can only be attributable to human error."); 355 return -1; 356 } 357 358 TAILQ_FOREACH(c, &s->connections, entry) { 359 if (c->state & CONN_FAILED) { 360 state = SESS_FAILED; 361 conn_fsm(c, CONN_EV_CLEANING_UP); 362 } else if (c->state & CONN_RUNNING && state != SESS_FAILED) 363 state = SESS_LOGGED_IN; 364 } 365 366 session_fsm(s, SESS_EV_START, NULL, s->holdTimer); 367 /* exponential back-off on constant failure */ 368 if (s->holdTimer < ISCSID_HOLD_TIME_MAX) 369 s->holdTimer = s->holdTimer ? s->holdTimer * 2 : 1; 370 371 return state; 372} 373 374int 375sess_do_conn_closed(struct session *s, struct sessev *sev) 376{ 377 struct connection *c = sev->conn; 378 int state = SESS_FREE; 379 380 if (c == NULL || c->state != CONN_FREE) { 381 log_warnx("Just what do you think you're doing, Dave?"); 382 return -1; 383 } 384 conn_free(c); 385 386 TAILQ_FOREACH(c, &s->connections, entry) { 387 if (c->state & CONN_FAILED) { 388 state = SESS_FAILED; 389 break; 390 } else if (c->state & CONN_RUNNING) 391 state = SESS_LOGGED_IN; 392 } 393 394 return state; 395} 396 397int 398sess_do_stop(struct session *s, struct sessev *sev) 399{ 400 struct connection *c; 401 402 /* XXX do graceful closing of session and go to INIT state at the end */ 403 404 while ((c = TAILQ_FIRST(&s->connections)) != NULL) 405 conn_free(c); 406 407 /* XXX anything else to reset to initial state? */ 408 return SESS_INIT; 409} 410 411int 412sess_do_free(struct session *s, struct sessev *sev) 413{ 414 struct connection *c; 415 416 while ((c = TAILQ_FIRST(&s->connections)) != NULL) 417 conn_free(c); 418 419 return SESS_FREE; 420} 421 422const char *conn_state(int); 423 424 425int 426sess_do_reinstatement(struct session *s, struct sessev *sev) 427{ 428 struct connection *c, *nc; 429 430 TAILQ_FOREACH_SAFE(c, &s->connections, entry, nc) { 431 log_debug("sess reinstatement[%s]: %s", 432 s->config.SessionName, conn_state(c->state)); 433 434 if (c->state & CONN_FAILED) { 435 conn_fsm(c, CONN_EV_FREE); 436 TAILQ_REMOVE(&s->connections, c, entry); 437 conn_free(c); 438 } 439 } 440 441 return SESS_LOGGED_IN; 442} 443 444const char * 445sess_state(int s) 446{ 447 static char buf[15]; 448 449 switch (s) { 450 case SESS_INIT: 451 return "INIT"; 452 case SESS_FREE: 453 return "FREE"; 454 case SESS_LOGGED_IN: 455 return "LOGGED_IN"; 456 case SESS_FAILED: 457 return "FAILED"; 458 default: 459 snprintf(buf, sizeof(buf), "UKNWN %x", s); 460 return buf; 461 } 462 /* NOTREACHED */ 463} 464 465const char * 466sess_event(enum s_event e) 467{ 468 static char buf[15]; 469 470 switch (e) { 471 case SESS_EV_START: 472 return "start"; 473 case SESS_EV_STOP: 474 return "stop"; 475 case SESS_EV_CONN_LOGGED_IN: 476 return "connection logged in"; 477 case SESS_EV_CONN_FAIL: 478 return "connection fail"; 479 case SESS_EV_CONN_CLOSED: 480 return "connection closed"; 481 case SESS_EV_REINSTATEMENT: 482 return "connection reinstated"; 483 case SESS_EV_CLOSED: 484 return "session closed"; 485 case SESS_EV_TIMEOUT: 486 return "timeout"; 487 case SESS_EV_FREE: 488 return "free"; 489 case SESS_EV_FAIL: 490 return "fail"; 491 } 492 493 snprintf(buf, sizeof(buf), "UKNWN %d", e); 494 return buf; 495} 496