1 /* $OpenBSD: session.c,v 1.9 2023/03/08 04:43:13 guenther 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 void 250 session_fsm_callback(int fd, short event, void *arg) 251 { 252 struct sessev *sev = arg; 253 struct session *s = sev->sess; 254 int i, ns; 255 256 for (i = 0; s_fsm[i].action != NULL; i++) { 257 if (s->state & s_fsm[i].state && 258 sev->event == s_fsm[i].event) { 259 log_debug("sess_fsm[%s]: %s ev %s", 260 s->config.SessionName, sess_state(s->state), 261 sess_event(sev->event)); 262 ns = s_fsm[i].action(s, sev); 263 if (ns == -1) 264 /* XXX better please */ 265 fatalx("sess_fsm: action failed"); 266 log_debug("sess_fsm[%s]: new state %s", 267 s->config.SessionName, 268 sess_state(ns)); 269 s->state = ns; 270 break; 271 } 272 } 273 if (s_fsm[i].action == NULL) { 274 log_warnx("sess_fsm[%s]: unhandled state transition " 275 "[%s, %s]", s->config.SessionName, 276 sess_state(s->state), sess_event(sev->event)); 277 fatalx("bjork bjork bjork"); 278 } 279 free(sev); 280 log_debug("sess_fsm: done"); 281 } 282 283 int 284 sess_do_start(struct session *s, struct sessev *sev) 285 { 286 log_debug("new connection to %s", 287 log_sockaddr(&s->config.connection.TargetAddr)); 288 289 /* initialize the session params */ 290 s->mine = initiator_sess_defaults; 291 s->his = iscsi_sess_defaults; 292 s->active = iscsi_sess_defaults; 293 294 if (s->config.SessionType != SESSION_TYPE_DISCOVERY && 295 s->config.MaxConnections) 296 s->mine.MaxConnections = s->config.MaxConnections; 297 298 conn_new(s, &s->config.connection); 299 300 /* XXX kill SESS_FREE it seems to be bad */ 301 if (s->state == SESS_INIT) 302 return SESS_FREE; 303 else 304 return s->state; 305 } 306 307 int 308 sess_do_conn_loggedin(struct session *s, struct sessev *sev) 309 { 310 if (s->state & SESS_LOGGED_IN) 311 return SESS_LOGGED_IN; 312 313 if (s->config.SessionType == SESSION_TYPE_DISCOVERY) { 314 initiator_discovery(s); 315 return SESS_LOGGED_IN; 316 } 317 318 iscsi_merge_sess_params(&s->active, &s->mine, &s->his); 319 vscsi_event(VSCSI_REQPROBE, s->target, -1); 320 s->holdTimer = 0; 321 322 return SESS_LOGGED_IN; 323 } 324 325 int 326 sess_do_conn_fail(struct session *s, struct sessev *sev) 327 { 328 struct connection *c = sev->conn; 329 int state = SESS_FREE; 330 331 if (sev->conn == NULL) { 332 log_warnx("Just what do you think you're doing, Dave?"); 333 return -1; 334 } 335 336 /* 337 * cleanup connections: 338 * Connections in state FREE can be removed. 339 * Connections in any error state will cause the session to enter 340 * the FAILED state. If no sessions are left and the session was 341 * not already FREE then implicit recovery needs to be done. 342 */ 343 344 switch (c->state) { 345 case CONN_FREE: 346 conn_free(c); 347 break; 348 case CONN_CLEANUP_WAIT: 349 break; 350 default: 351 log_warnx("It can only be attributable to human error."); 352 return -1; 353 } 354 355 TAILQ_FOREACH(c, &s->connections, entry) { 356 if (c->state & CONN_FAILED) { 357 state = SESS_FAILED; 358 conn_fsm(c, CONN_EV_CLEANING_UP); 359 } else if (c->state & CONN_RUNNING && state != SESS_FAILED) 360 state = SESS_LOGGED_IN; 361 } 362 363 session_fsm(s, SESS_EV_START, NULL, s->holdTimer); 364 /* exponential back-off on constant failure */ 365 if (s->holdTimer < ISCSID_HOLD_TIME_MAX) 366 s->holdTimer = s->holdTimer ? s->holdTimer * 2 : 1; 367 368 return state; 369 } 370 371 int 372 sess_do_conn_closed(struct session *s, struct sessev *sev) 373 { 374 struct connection *c = sev->conn; 375 int state = SESS_FREE; 376 377 if (c == NULL || c->state != CONN_FREE) { 378 log_warnx("Just what do you think you're doing, Dave?"); 379 return -1; 380 } 381 conn_free(c); 382 383 TAILQ_FOREACH(c, &s->connections, entry) { 384 if (c->state & CONN_FAILED) { 385 state = SESS_FAILED; 386 break; 387 } else if (c->state & CONN_RUNNING) 388 state = SESS_LOGGED_IN; 389 } 390 391 return state; 392 } 393 394 int 395 sess_do_stop(struct session *s, struct sessev *sev) 396 { 397 struct connection *c; 398 399 /* XXX do graceful closing of session and go to INIT state at the end */ 400 401 while ((c = TAILQ_FIRST(&s->connections)) != NULL) 402 conn_free(c); 403 404 /* XXX anything else to reset to initial state? */ 405 return SESS_INIT; 406 } 407 408 int 409 sess_do_free(struct session *s, struct sessev *sev) 410 { 411 struct connection *c; 412 413 while ((c = TAILQ_FIRST(&s->connections)) != NULL) 414 conn_free(c); 415 416 return SESS_FREE; 417 } 418 419 const char *conn_state(int); 420 421 422 int 423 sess_do_reinstatement(struct session *s, struct sessev *sev) 424 { 425 struct connection *c, *nc; 426 427 TAILQ_FOREACH_SAFE(c, &s->connections, entry, nc) { 428 log_debug("sess reinstatement[%s]: %s", 429 s->config.SessionName, conn_state(c->state)); 430 431 if (c->state & CONN_FAILED) { 432 conn_fsm(c, CONN_EV_FREE); 433 TAILQ_REMOVE(&s->connections, c, entry); 434 conn_free(c); 435 } 436 } 437 438 return SESS_LOGGED_IN; 439 } 440 441 const char * 442 sess_state(int s) 443 { 444 static char buf[15]; 445 446 switch (s) { 447 case SESS_INIT: 448 return "INIT"; 449 case SESS_FREE: 450 return "FREE"; 451 case SESS_LOGGED_IN: 452 return "LOGGED_IN"; 453 case SESS_FAILED: 454 return "FAILED"; 455 default: 456 snprintf(buf, sizeof(buf), "UKNWN %x", s); 457 return buf; 458 } 459 /* NOTREACHED */ 460 } 461 462 const char * 463 sess_event(enum s_event e) 464 { 465 static char buf[15]; 466 467 switch (e) { 468 case SESS_EV_START: 469 return "start"; 470 case SESS_EV_STOP: 471 return "stop"; 472 case SESS_EV_CONN_LOGGED_IN: 473 return "connection logged in"; 474 case SESS_EV_CONN_FAIL: 475 return "connection fail"; 476 case SESS_EV_CONN_CLOSED: 477 return "connection closed"; 478 case SESS_EV_REINSTATEMENT: 479 return "connection reinstated"; 480 case SESS_EV_CLOSED: 481 return "session closed"; 482 case SESS_EV_TIMEOUT: 483 return "timeout"; 484 case SESS_EV_FREE: 485 return "free"; 486 case SESS_EV_FAIL: 487 return "fail"; 488 } 489 490 snprintf(buf, sizeof(buf), "UKNWN %d", e); 491 return buf; 492 } 493