xref: /openbsd-src/usr.sbin/iscsid/session.c (revision 46035553bfdd96e63c94e32da0210227ec2e3cf1)
1 /*	$OpenBSD: session.c,v 1.8 2015/12/05 06:37:24 mmcc 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 /* ARGSUSED */
250 void
251 session_fsm_callback(int fd, short event, void *arg)
252 {
253 	struct sessev *sev = arg;
254 	struct session *s = sev->sess;
255 	int	i, ns;
256 
257 	for (i = 0; s_fsm[i].action != NULL; i++) {
258 		if (s->state & s_fsm[i].state &&
259 		    sev->event == s_fsm[i].event) {
260 			log_debug("sess_fsm[%s]: %s ev %s",
261 			    s->config.SessionName, sess_state(s->state),
262 			    sess_event(sev->event));
263 			ns = s_fsm[i].action(s, sev);
264 			if (ns == -1)
265 				/* XXX better please */
266 				fatalx("sess_fsm: action failed");
267 			log_debug("sess_fsm[%s]: new state %s",
268 			    s->config.SessionName,
269 			    sess_state(ns));
270 			s->state = ns;
271 			break;
272 		}
273 	}
274 	if (s_fsm[i].action == NULL) {
275 		log_warnx("sess_fsm[%s]: unhandled state transition "
276 		    "[%s, %s]", s->config.SessionName,
277 		    sess_state(s->state), sess_event(sev->event));
278 		fatalx("bjork bjork bjork");
279 	}
280 	free(sev);
281 log_debug("sess_fsm: done");
282 }
283 
284 int
285 sess_do_start(struct session *s, struct sessev *sev)
286 {
287 	log_debug("new connection to %s",
288 	    log_sockaddr(&s->config.connection.TargetAddr));
289 
290 	/* initialize the session params */
291 	s->mine = initiator_sess_defaults;
292 	s->his = iscsi_sess_defaults;
293 	s->active = iscsi_sess_defaults;
294 
295 	if (s->config.SessionType != SESSION_TYPE_DISCOVERY &&
296 	    s->config.MaxConnections)
297 		s->mine.MaxConnections = s->config.MaxConnections;
298 
299 	conn_new(s, &s->config.connection);
300 
301 	/* XXX kill SESS_FREE it seems to be bad */
302 	if (s->state == SESS_INIT)
303 		return SESS_FREE;
304 	else
305 		return s->state;
306 }
307 
308 int
309 sess_do_conn_loggedin(struct session *s, struct sessev *sev)
310 {
311 	if (s->state & SESS_LOGGED_IN)
312 		return SESS_LOGGED_IN;
313 
314 	if (s->config.SessionType == SESSION_TYPE_DISCOVERY) {
315 		initiator_discovery(s);
316 		return SESS_LOGGED_IN;
317 	}
318 
319 	iscsi_merge_sess_params(&s->active, &s->mine, &s->his);
320 	vscsi_event(VSCSI_REQPROBE, s->target, -1);
321 	s->holdTimer = 0;
322 
323 	return SESS_LOGGED_IN;
324 }
325 
326 int
327 sess_do_conn_fail(struct session *s, struct sessev *sev)
328 {
329 	struct connection *c = sev->conn;
330 	int state = SESS_FREE;
331 
332 	if (sev->conn == NULL) {
333 		log_warnx("Just what do you think you're doing, Dave?");
334 		return -1;
335 	}
336 
337 	/*
338 	 * cleanup connections:
339 	 * Connections in state FREE can be removed.
340 	 * Connections in any error state will cause the session to enter
341 	 * the FAILED state. If no sessions are left and the session was
342 	 * not already FREE then implicit recovery needs to be done.
343 	 */
344 
345 	switch (c->state) {
346 	case CONN_FREE:
347 		conn_free(c);
348 		break;
349 	case CONN_CLEANUP_WAIT:
350 		break;
351 	default:
352 		log_warnx("It can only be attributable to human error.");
353 		return -1;
354 	}
355 
356 	TAILQ_FOREACH(c, &s->connections, entry) {
357 		if (c->state & CONN_FAILED) {
358 			state = SESS_FAILED;
359 			conn_fsm(c, CONN_EV_CLEANING_UP);
360 		} else if (c->state & CONN_RUNNING && state != SESS_FAILED)
361 			state = SESS_LOGGED_IN;
362 	}
363 
364 	session_fsm(s, SESS_EV_START, NULL, s->holdTimer);
365 	/* exponential back-off on constant failure */
366 	if (s->holdTimer < ISCSID_HOLD_TIME_MAX)
367 		s->holdTimer = s->holdTimer ? s->holdTimer * 2 : 1;
368 
369 	return state;
370 }
371 
372 int
373 sess_do_conn_closed(struct session *s, struct sessev *sev)
374 {
375 	struct connection *c = sev->conn;
376 	int state = SESS_FREE;
377 
378 	if (c == NULL || c->state != CONN_FREE) {
379 		log_warnx("Just what do you think you're doing, Dave?");
380 		return -1;
381 	}
382 	conn_free(c);
383 
384 	TAILQ_FOREACH(c, &s->connections, entry) {
385 		if (c->state & CONN_FAILED) {
386 			state = SESS_FAILED;
387 			break;
388 		} else if (c->state & CONN_RUNNING)
389 			state = SESS_LOGGED_IN;
390 	}
391 
392 	return state;
393 }
394 
395 int
396 sess_do_stop(struct session *s, struct sessev *sev)
397 {
398 	struct connection *c;
399 
400 	/* XXX do graceful closing of session and go to INIT state at the end */
401 
402 	while ((c = TAILQ_FIRST(&s->connections)) != NULL)
403 		conn_free(c);
404 
405 	/* XXX anything else to reset to initial state? */
406 	return SESS_INIT;
407 }
408 
409 int
410 sess_do_free(struct session *s, struct sessev *sev)
411 {
412 	struct connection *c;
413 
414 	while ((c = TAILQ_FIRST(&s->connections)) != NULL)
415 		conn_free(c);
416 
417 	return SESS_FREE;
418 }
419 
420 const char *conn_state(int);
421 
422 
423 int
424 sess_do_reinstatement(struct session *s, struct sessev *sev)
425 {
426 	struct connection *c, *nc;
427 
428 	TAILQ_FOREACH_SAFE(c, &s->connections, entry, nc) {
429 		log_debug("sess reinstatement[%s]: %s",
430 		    s->config.SessionName, conn_state(c->state));
431 
432 		if (c->state & CONN_FAILED) {
433 			conn_fsm(c, CONN_EV_FREE);
434 			TAILQ_REMOVE(&s->connections, c, entry);
435 			conn_free(c);
436 		}
437 	}
438 
439 	return SESS_LOGGED_IN;
440 }
441 
442 const char *
443 sess_state(int s)
444 {
445 	static char buf[15];
446 
447 	switch (s) {
448 	case SESS_INIT:
449 		return "INIT";
450 	case SESS_FREE:
451 		return "FREE";
452 	case SESS_LOGGED_IN:
453 		return "LOGGED_IN";
454 	case SESS_FAILED:
455 		return "FAILED";
456 	default:
457 		snprintf(buf, sizeof(buf), "UKNWN %x", s);
458 		return buf;
459 	}
460 	/* NOTREACHED */
461 }
462 
463 const char *
464 sess_event(enum s_event e)
465 {
466 	static char buf[15];
467 
468 	switch (e) {
469 	case SESS_EV_START:
470 		return "start";
471 	case SESS_EV_STOP:
472 		return "stop";
473 	case SESS_EV_CONN_LOGGED_IN:
474 		return "connection logged in";
475 	case SESS_EV_CONN_FAIL:
476 		return "connection fail";
477 	case SESS_EV_CONN_CLOSED:
478 		return "connection closed";
479 	case SESS_EV_REINSTATEMENT:
480 		return "connection reinstated";
481 	case SESS_EV_CLOSED:
482 		return "session closed";
483 	case SESS_EV_TIMEOUT:
484 		return "timeout";
485 	case SESS_EV_FREE:
486 		return "free";
487 	case SESS_EV_FAIL:
488 		return "fail";
489 	}
490 
491 	snprintf(buf, sizeof(buf), "UKNWN %d", e);
492 	return buf;
493 }
494