1 /* $OpenBSD: session.c,v 1.8 2015/12/05 06:37:24 mmcc 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 38 void session_fsm_callback(int, short, void *); 39 int sess_do_start(struct session *, struct sessev *); 40 int sess_do_conn_loggedin(struct session *, struct sessev *); 41 int sess_do_conn_fail(struct session *, struct sessev *); 42 int sess_do_conn_closed(struct session *, struct sessev *); 43 int sess_do_stop(struct session *, struct sessev *); 44 int sess_do_free(struct session *, struct sessev *); 45 int sess_do_reinstatement(struct session *, struct sessev *); 46 47 const char *sess_state(int); 48 const char *sess_event(enum s_event); 49 50 struct session * 51 session_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 62 struct session * 63 session_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 90 void 91 session_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 105 int 106 session_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 126 void 127 session_config(struct session *s, struct session_config *sc) 128 { 129 free(s->config.TargetName); 130 s->config.TargetName = NULL; 131 free(s->config.InitiatorName); 132 s->config.InitiatorName = NULL; 133 134 s->config = *sc; 135 136 if (sc->TargetName) { 137 s->config.TargetName = strdup(sc->TargetName); 138 if (s->config.TargetName == NULL) 139 fatal("strdup"); 140 } 141 if (sc->InitiatorName) { 142 s->config.InitiatorName = strdup(sc->InitiatorName); 143 if (s->config.InitiatorName == NULL) 144 fatal("strdup"); 145 } else 146 s->config.InitiatorName = default_initiator_name(); 147 } 148 149 void 150 session_task_issue(struct session *s, struct task *t) 151 { 152 TAILQ_INSERT_TAIL(&s->tasks, t, entry); 153 session_schedule(s); 154 } 155 156 void 157 session_logout_issue(struct session *s, struct task *t) 158 { 159 struct connection *c, *rc = NULL; 160 161 /* find first free session or first available session */ 162 TAILQ_FOREACH(c, &s->connections, entry) { 163 if (conn_task_ready(c)) { 164 conn_fsm(c, CONN_EV_LOGOUT); 165 conn_task_issue(c, t); 166 return; 167 } 168 if (c->state & CONN_RUNNING) 169 rc = c; 170 } 171 172 if (rc) { 173 conn_fsm(rc, CONN_EV_LOGOUT); 174 conn_task_issue(rc, t); 175 return; 176 } 177 178 /* XXX must open new connection, gulp */ 179 fatalx("session_logout_issue needs more work"); 180 } 181 182 void 183 session_schedule(struct session *s) 184 { 185 struct task *t = TAILQ_FIRST(&s->tasks); 186 struct connection *c; 187 188 if (!t) 189 return; 190 191 /* XXX IMMEDIATE TASK NEED SPECIAL HANDLING !!!! */ 192 193 /* wake up a idle connection or a not busy one */ 194 /* XXX this needs more work as it makes the daemon go wrooOOOMM */ 195 TAILQ_FOREACH(c, &s->connections, entry) 196 if (conn_task_ready(c)) { 197 TAILQ_REMOVE(&s->tasks, t, entry); 198 conn_task_issue(c, t); 199 return; 200 } 201 } 202 203 /* 204 * The session FSM runs from a callback so that the connection FSM can finish. 205 */ 206 void 207 session_fsm(struct session *s, enum s_event ev, struct connection *c, 208 unsigned int timeout) 209 { 210 struct timeval tv; 211 struct sessev *sev; 212 213 log_debug("session_fsm[%s]: %s ev %s timeout %d", 214 s->config.SessionName, sess_state(s->state), 215 sess_event(ev), timeout); 216 217 if ((sev = malloc(sizeof(*sev))) == NULL) 218 fatal("session_fsm"); 219 sev->conn = c; 220 sev->sess = s; 221 sev->event = ev; 222 223 timerclear(&tv); 224 tv.tv_sec = timeout; 225 if (event_once(-1, EV_TIMEOUT, session_fsm_callback, sev, &tv) == -1) 226 fatal("session_fsm"); 227 } 228 229 struct { 230 int state; 231 enum s_event event; 232 int (*action)(struct session *, struct sessev *); 233 } s_fsm[] = { 234 { SESS_INIT, SESS_EV_START, sess_do_start }, 235 { SESS_FREE, SESS_EV_START, sess_do_start }, 236 { SESS_FREE, SESS_EV_CONN_LOGGED_IN, sess_do_conn_loggedin }, /* N1 */ 237 { SESS_FREE, SESS_EV_CLOSED, sess_do_stop }, 238 { SESS_LOGGED_IN, SESS_EV_CONN_LOGGED_IN, sess_do_conn_loggedin }, 239 { SESS_RUNNING, SESS_EV_CONN_CLOSED, sess_do_conn_closed }, /* N3 */ 240 { SESS_RUNNING, SESS_EV_CONN_FAIL, sess_do_conn_fail }, /* N5 */ 241 { SESS_RUNNING, SESS_EV_CLOSED, sess_do_free }, /* XXX */ 242 { SESS_FAILED, SESS_EV_START, sess_do_start }, 243 { SESS_FAILED, SESS_EV_TIMEOUT, sess_do_free }, /* N6 */ 244 { SESS_FAILED, SESS_EV_FREE, sess_do_free }, /* N6 */ 245 { SESS_FAILED, SESS_EV_CONN_LOGGED_IN, sess_do_reinstatement }, /* N4 */ 246 { 0, 0, NULL } 247 }; 248 249 /* ARGSUSED */ 250 void 251 session_fsm_callback(int fd, short event, void *arg) 252 { 253 struct sessev *sev = arg; 254 struct session *s = sev->sess; 255 int i, ns; 256 257 for (i = 0; s_fsm[i].action != NULL; i++) { 258 if (s->state & s_fsm[i].state && 259 sev->event == s_fsm[i].event) { 260 log_debug("sess_fsm[%s]: %s ev %s", 261 s->config.SessionName, sess_state(s->state), 262 sess_event(sev->event)); 263 ns = s_fsm[i].action(s, sev); 264 if (ns == -1) 265 /* XXX better please */ 266 fatalx("sess_fsm: action failed"); 267 log_debug("sess_fsm[%s]: new state %s", 268 s->config.SessionName, 269 sess_state(ns)); 270 s->state = ns; 271 break; 272 } 273 } 274 if (s_fsm[i].action == NULL) { 275 log_warnx("sess_fsm[%s]: unhandled state transition " 276 "[%s, %s]", s->config.SessionName, 277 sess_state(s->state), sess_event(sev->event)); 278 fatalx("bjork bjork bjork"); 279 } 280 free(sev); 281 log_debug("sess_fsm: done"); 282 } 283 284 int 285 sess_do_start(struct session *s, struct sessev *sev) 286 { 287 log_debug("new connection to %s", 288 log_sockaddr(&s->config.connection.TargetAddr)); 289 290 /* initialize the session params */ 291 s->mine = initiator_sess_defaults; 292 s->his = iscsi_sess_defaults; 293 s->active = iscsi_sess_defaults; 294 295 if (s->config.SessionType != SESSION_TYPE_DISCOVERY && 296 s->config.MaxConnections) 297 s->mine.MaxConnections = s->config.MaxConnections; 298 299 conn_new(s, &s->config.connection); 300 301 /* XXX kill SESS_FREE it seems to be bad */ 302 if (s->state == SESS_INIT) 303 return SESS_FREE; 304 else 305 return s->state; 306 } 307 308 int 309 sess_do_conn_loggedin(struct session *s, struct sessev *sev) 310 { 311 if (s->state & SESS_LOGGED_IN) 312 return SESS_LOGGED_IN; 313 314 if (s->config.SessionType == SESSION_TYPE_DISCOVERY) { 315 initiator_discovery(s); 316 return SESS_LOGGED_IN; 317 } 318 319 iscsi_merge_sess_params(&s->active, &s->mine, &s->his); 320 vscsi_event(VSCSI_REQPROBE, s->target, -1); 321 s->holdTimer = 0; 322 323 return SESS_LOGGED_IN; 324 } 325 326 int 327 sess_do_conn_fail(struct session *s, struct sessev *sev) 328 { 329 struct connection *c = sev->conn; 330 int state = SESS_FREE; 331 332 if (sev->conn == NULL) { 333 log_warnx("Just what do you think you're doing, Dave?"); 334 return -1; 335 } 336 337 /* 338 * cleanup connections: 339 * Connections in state FREE can be removed. 340 * Connections in any error state will cause the session to enter 341 * the FAILED state. If no sessions are left and the session was 342 * not already FREE then implicit recovery needs to be done. 343 */ 344 345 switch (c->state) { 346 case CONN_FREE: 347 conn_free(c); 348 break; 349 case CONN_CLEANUP_WAIT: 350 break; 351 default: 352 log_warnx("It can only be attributable to human error."); 353 return -1; 354 } 355 356 TAILQ_FOREACH(c, &s->connections, entry) { 357 if (c->state & CONN_FAILED) { 358 state = SESS_FAILED; 359 conn_fsm(c, CONN_EV_CLEANING_UP); 360 } else if (c->state & CONN_RUNNING && state != SESS_FAILED) 361 state = SESS_LOGGED_IN; 362 } 363 364 session_fsm(s, SESS_EV_START, NULL, s->holdTimer); 365 /* exponential back-off on constant failure */ 366 if (s->holdTimer < ISCSID_HOLD_TIME_MAX) 367 s->holdTimer = s->holdTimer ? s->holdTimer * 2 : 1; 368 369 return state; 370 } 371 372 int 373 sess_do_conn_closed(struct session *s, struct sessev *sev) 374 { 375 struct connection *c = sev->conn; 376 int state = SESS_FREE; 377 378 if (c == NULL || c->state != CONN_FREE) { 379 log_warnx("Just what do you think you're doing, Dave?"); 380 return -1; 381 } 382 conn_free(c); 383 384 TAILQ_FOREACH(c, &s->connections, entry) { 385 if (c->state & CONN_FAILED) { 386 state = SESS_FAILED; 387 break; 388 } else if (c->state & CONN_RUNNING) 389 state = SESS_LOGGED_IN; 390 } 391 392 return state; 393 } 394 395 int 396 sess_do_stop(struct session *s, struct sessev *sev) 397 { 398 struct connection *c; 399 400 /* XXX do graceful closing of session and go to INIT state at the end */ 401 402 while ((c = TAILQ_FIRST(&s->connections)) != NULL) 403 conn_free(c); 404 405 /* XXX anything else to reset to initial state? */ 406 return SESS_INIT; 407 } 408 409 int 410 sess_do_free(struct session *s, struct sessev *sev) 411 { 412 struct connection *c; 413 414 while ((c = TAILQ_FIRST(&s->connections)) != NULL) 415 conn_free(c); 416 417 return SESS_FREE; 418 } 419 420 const char *conn_state(int); 421 422 423 int 424 sess_do_reinstatement(struct session *s, struct sessev *sev) 425 { 426 struct connection *c, *nc; 427 428 TAILQ_FOREACH_SAFE(c, &s->connections, entry, nc) { 429 log_debug("sess reinstatement[%s]: %s", 430 s->config.SessionName, conn_state(c->state)); 431 432 if (c->state & CONN_FAILED) { 433 conn_fsm(c, CONN_EV_FREE); 434 TAILQ_REMOVE(&s->connections, c, entry); 435 conn_free(c); 436 } 437 } 438 439 return SESS_LOGGED_IN; 440 } 441 442 const char * 443 sess_state(int s) 444 { 445 static char buf[15]; 446 447 switch (s) { 448 case SESS_INIT: 449 return "INIT"; 450 case SESS_FREE: 451 return "FREE"; 452 case SESS_LOGGED_IN: 453 return "LOGGED_IN"; 454 case SESS_FAILED: 455 return "FAILED"; 456 default: 457 snprintf(buf, sizeof(buf), "UKNWN %x", s); 458 return buf; 459 } 460 /* NOTREACHED */ 461 } 462 463 const char * 464 sess_event(enum s_event e) 465 { 466 static char buf[15]; 467 468 switch (e) { 469 case SESS_EV_START: 470 return "start"; 471 case SESS_EV_STOP: 472 return "stop"; 473 case SESS_EV_CONN_LOGGED_IN: 474 return "connection logged in"; 475 case SESS_EV_CONN_FAIL: 476 return "connection fail"; 477 case SESS_EV_CONN_CLOSED: 478 return "connection closed"; 479 case SESS_EV_REINSTATEMENT: 480 return "connection reinstated"; 481 case SESS_EV_CLOSED: 482 return "session closed"; 483 case SESS_EV_TIMEOUT: 484 return "timeout"; 485 case SESS_EV_FREE: 486 return "free"; 487 case SESS_EV_FAIL: 488 return "fail"; 489 } 490 491 snprintf(buf, sizeof(buf), "UKNWN %d", e); 492 return buf; 493 } 494