xref: /openbsd/usr.sbin/iscsid/session.c (revision 4bdff4be)
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