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
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	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
149void
150session_task_issue(struct session *s, struct task *t)
151{
152	TAILQ_INSERT_TAIL(&s->tasks, t, entry);
153	session_schedule(s);
154}
155
156void
157session_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
182void
183session_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 */
206void
207session_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
229struct {
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
249void
250session_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);
280log_debug("sess_fsm: done");
281}
282
283int
284sess_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
307int
308sess_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
325int
326sess_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
371int
372sess_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
394int
395sess_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
408int
409sess_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
419const char *conn_state(int);
420
421
422int
423sess_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
441const char *
442sess_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
462const char *
463sess_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