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