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