xref: /openbsd-src/usr.sbin/iscsid/session.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
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 
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 	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 
151 void
152 session_task_issue(struct session *s, struct task *t)
153 {
154 	TAILQ_INSERT_TAIL(&s->tasks, t, entry);
155 	session_schedule(s);
156 }
157 
158 void
159 session_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 
184 void
185 session_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  */
208 void
209 session_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 
231 struct {
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 */
252 void
253 session_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);
283 log_debug("sess_fsm: done");
284 }
285 
286 int
287 sess_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 
310 int
311 sess_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 
328 int
329 sess_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 
374 int
375 sess_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 
397 int
398 sess_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 
411 int
412 sess_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 
422 const char *conn_state(int);
423 
424 
425 int
426 sess_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 
444 const char *
445 sess_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 
465 const char *
466 sess_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