1 /* $OpenBSD: session.c,v 1.13 2025/01/22 16:06:36 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 int sess_do_start(struct session *, struct sessev *);
39 int sess_do_conn_loggedin(struct session *, struct sessev *);
40 int sess_do_conn_fail(struct session *, struct sessev *);
41 int sess_do_conn_closed(struct session *, struct sessev *);
42 int sess_do_stop(struct session *, struct sessev *);
43 int sess_do_free(struct session *, struct sessev *);
44 int sess_do_reinstatement(struct session *, struct sessev *);
45
46 const char *sess_state(int);
47 const char *sess_event(enum s_event);
48
49 void
session_cleanup(struct session * s)50 session_cleanup(struct session *s)
51 {
52 struct connection *c;
53
54 taskq_cleanup(&s->tasks);
55
56 while ((c = TAILQ_FIRST(&s->connections)) != NULL)
57 conn_free(c);
58
59 free(s->config.TargetName);
60 free(s->config.InitiatorName);
61 free(s);
62 }
63
64 int
session_shutdown(struct session * s)65 session_shutdown(struct session *s)
66 {
67 log_debug("session[%s] going down", s->config.SessionName);
68
69 s->action = SESS_ACT_DOWN;
70 if (s->state & (SESS_INIT | SESS_FREE)) {
71 /* no active session, so do a quick cleanup */
72 struct connection *c;
73 while ((c = TAILQ_FIRST(&s->connections)) != NULL)
74 conn_free(c);
75 return 0;
76 }
77
78 /* cleanup task queue and issue a logout */
79 taskq_cleanup(&s->tasks);
80 initiator_logout(s, NULL, ISCSI_LOGOUT_CLOSE_SESS);
81
82 return 1;
83 }
84
85 void
session_config(struct session * s,struct session_config * sc)86 session_config(struct session *s, struct session_config *sc)
87 {
88 free(s->config.TargetName);
89 s->config.TargetName = NULL;
90 free(s->config.InitiatorName);
91 s->config.InitiatorName = NULL;
92
93 s->config = *sc;
94
95 if (sc->TargetName) {
96 s->config.TargetName = strdup(sc->TargetName);
97 if (s->config.TargetName == NULL)
98 fatal("strdup");
99 }
100 if (sc->InitiatorName) {
101 s->config.InitiatorName = strdup(sc->InitiatorName);
102 if (s->config.InitiatorName == NULL)
103 fatal("strdup");
104 } else
105 s->config.InitiatorName = default_initiator_name();
106 }
107
108 void
session_task_issue(struct session * s,struct task * t)109 session_task_issue(struct session *s, struct task *t)
110 {
111 TAILQ_INSERT_TAIL(&s->tasks, t, entry);
112 session_schedule(s);
113 }
114
115 void
session_logout_issue(struct session * s,struct task * t)116 session_logout_issue(struct session *s, struct task *t)
117 {
118 struct connection *c, *rc = NULL;
119
120 /* find first free session or first available session */
121 TAILQ_FOREACH(c, &s->connections, entry) {
122 if (conn_task_ready(c)) {
123 conn_fsm(c, CONN_EV_LOGOUT);
124 conn_task_issue(c, t);
125 return;
126 }
127 if (c->state & CONN_RUNNING)
128 rc = c;
129 }
130
131 if (rc) {
132 conn_fsm(rc, CONN_EV_LOGOUT);
133 conn_task_issue(rc, t);
134 return;
135 }
136
137 /* XXX must open new connection, gulp */
138 fatalx("session_logout_issue needs more work");
139 }
140
141 void
session_schedule(struct session * s)142 session_schedule(struct session *s)
143 {
144 struct task *t = TAILQ_FIRST(&s->tasks);
145 struct connection *c;
146
147 if (!t)
148 return;
149
150 /* XXX IMMEDIATE TASK NEED SPECIAL HANDLING !!!! */
151
152 /* wake up a idle connection or a not busy one */
153 /* XXX this needs more work as it makes the daemon go wrooOOOMM */
154 TAILQ_FOREACH(c, &s->connections, entry)
155 if (conn_task_ready(c)) {
156 TAILQ_REMOVE(&s->tasks, t, entry);
157 conn_task_issue(c, t);
158 return;
159 }
160 }
161
162 /*
163 * The session FSM runs from a callback so that the connection FSM can finish.
164 */
165 void
session_fsm(struct sessev * sev,enum s_event event,unsigned int timeout)166 session_fsm(struct sessev *sev, enum s_event event, unsigned int timeout)
167 {
168 struct session *s = sev->sess;
169 struct timeval tv;
170
171 log_debug("session_fsm[%s]: %s ev %s timeout %d",
172 s->config.SessionName, sess_state(s->state),
173 sess_event(event), timeout);
174
175 sev->event = event;
176
177 timerclear(&tv);
178 tv.tv_sec = timeout;
179 if (evtimer_add(&sev->ev, &tv) == -1)
180 fatal("session_fsm");
181 }
182
183 struct {
184 int state;
185 enum s_event event;
186 int (*action)(struct session *, struct sessev *);
187 } s_fsm[] = {
188 { SESS_INIT, SESS_EV_START, sess_do_start },
189 { SESS_FREE, SESS_EV_START, sess_do_start },
190 { SESS_FREE, SESS_EV_CONN_LOGGED_IN, sess_do_conn_loggedin }, /* N1 */
191 { SESS_FREE, SESS_EV_CLOSED, sess_do_stop },
192 { SESS_LOGGED_IN, SESS_EV_CONN_LOGGED_IN, sess_do_conn_loggedin },
193 { SESS_RUNNING, SESS_EV_CONN_CLOSED, sess_do_conn_closed }, /* N3 */
194 { SESS_RUNNING, SESS_EV_CONN_FAIL, sess_do_conn_fail }, /* N5 */
195 { SESS_RUNNING, SESS_EV_CLOSED, sess_do_free }, /* XXX */
196 { SESS_FAILED, SESS_EV_START, sess_do_start },
197 { SESS_FAILED, SESS_EV_TIMEOUT, sess_do_free }, /* N6 */
198 { SESS_FAILED, SESS_EV_FREE, sess_do_free }, /* N6 */
199 { SESS_FAILED, SESS_EV_CONN_LOGGED_IN, sess_do_reinstatement }, /* N4 */
200 { 0, 0, NULL }
201 };
202
203 void
session_fsm_callback(int fd,short event,void * arg)204 session_fsm_callback(int fd, short event, void *arg)
205 {
206 struct sessev *sev = arg;
207 struct session *s = sev->sess;
208 int i, ns;
209
210 for (i = 0; s_fsm[i].action != NULL; i++) {
211 if (s->state & s_fsm[i].state &&
212 sev->event == s_fsm[i].event) {
213 log_debug("sess_fsm[%s]: %s ev %s",
214 s->config.SessionName, sess_state(s->state),
215 sess_event(sev->event));
216 ns = s_fsm[i].action(s, sev);
217 if (ns == -1)
218 /* XXX better please */
219 fatalx("sess_fsm: action failed");
220 log_debug("sess_fsm[%s]: new state %s",
221 s->config.SessionName,
222 sess_state(ns));
223 s->state = ns;
224 break;
225 }
226 }
227 if (s_fsm[i].action == NULL) {
228 log_warnx("sess_fsm[%s]: unhandled state transition "
229 "[%s, %s]", s->config.SessionName,
230 sess_state(s->state), sess_event(sev->event));
231 fatalx("bjork bjork bjork");
232 }
233 }
234
235 int
sess_do_start(struct session * s,struct sessev * sev)236 sess_do_start(struct session *s, struct sessev *sev)
237 {
238 log_debug("new connection to %s",
239 log_sockaddr(&s->config.connection.TargetAddr));
240
241 /* initialize the session params, and reset the active state */
242 s->mine = initiator_sess_defaults;
243 s->his = iscsi_sess_defaults;
244 s->active = iscsi_sess_defaults;
245
246 if (s->config.SessionType != SESSION_TYPE_DISCOVERY &&
247 s->config.MaxConnections)
248 s->mine.MaxConnections = s->config.MaxConnections;
249
250 conn_new(s, &s->config.connection);
251
252 /* XXX kill SESS_FREE it seems to be bad */
253 if (s->state == SESS_INIT)
254 return SESS_FREE;
255 else
256 return s->state;
257 }
258
259 int
sess_do_conn_loggedin(struct session * s,struct sessev * sev)260 sess_do_conn_loggedin(struct session *s, struct sessev *sev)
261 {
262 if (s->state & SESS_LOGGED_IN)
263 return SESS_LOGGED_IN;
264
265 if (s->config.SessionType == SESSION_TYPE_DISCOVERY) {
266 initiator_discovery(s);
267 return SESS_LOGGED_IN;
268 }
269
270 iscsi_merge_sess_params(&s->active, &s->mine, &s->his);
271 vscsi_event(VSCSI_REQPROBE, s->target, -1);
272 s->holdTimer = 0;
273
274 return SESS_LOGGED_IN;
275 }
276
277 int
sess_do_conn_fail(struct session * s,struct sessev * sev)278 sess_do_conn_fail(struct session *s, struct sessev *sev)
279 {
280 struct connection *c = sev->conn;
281 int state = SESS_FREE;
282
283 if (sev->conn == NULL) {
284 log_warnx("Just what do you think you're doing, Dave?");
285 return -1;
286 }
287
288 /*
289 * cleanup connections:
290 * Connections in state FREE can be removed.
291 * Connections in any error state will cause the session to enter
292 * the FAILED state. If no sessions are left and the session was
293 * not already FREE then implicit recovery needs to be done.
294 */
295
296 switch (c->state) {
297 case CONN_FREE:
298 conn_free(c);
299 break;
300 case CONN_CLEANUP_WAIT:
301 break;
302 default:
303 log_warnx("It can only be attributable to human error.");
304 return -1;
305 }
306
307 TAILQ_FOREACH(c, &s->connections, entry) {
308 if (c->state & CONN_FAILED) {
309 state = SESS_FAILED;
310 conn_fsm(c, CONN_EV_CLEANING_UP);
311 } else if (c->state & CONN_RUNNING && state != SESS_FAILED)
312 state = SESS_LOGGED_IN;
313 }
314
315 session_fsm(&s->sev, SESS_EV_START, s->holdTimer);
316 /* exponential back-off on constant failure */
317 if (s->holdTimer < ISCSID_HOLD_TIME_MAX)
318 s->holdTimer = s->holdTimer ? s->holdTimer * 2 : 1;
319
320 return state;
321 }
322
323 int
sess_do_conn_closed(struct session * s,struct sessev * sev)324 sess_do_conn_closed(struct session *s, struct sessev *sev)
325 {
326 struct connection *c = sev->conn;
327 int state = SESS_FREE;
328
329 if (c == NULL || c->state != CONN_FREE) {
330 log_warnx("Just what do you think you're doing, Dave?");
331 return -1;
332 }
333 conn_free(c);
334
335 TAILQ_FOREACH(c, &s->connections, entry) {
336 if (c->state & CONN_FAILED) {
337 state = SESS_FAILED;
338 break;
339 } else if (c->state & CONN_RUNNING)
340 state = SESS_LOGGED_IN;
341 }
342
343 return state;
344 }
345
346 int
sess_do_stop(struct session * s,struct sessev * sev)347 sess_do_stop(struct session *s, struct sessev *sev)
348 {
349 struct connection *c;
350
351 /* XXX do graceful closing of session and go to INIT state at the end */
352
353 while ((c = TAILQ_FIRST(&s->connections)) != NULL)
354 conn_free(c);
355
356 /* XXX anything else to reset to initial state? */
357 return SESS_INIT;
358 }
359
360 int
sess_do_free(struct session * s,struct sessev * sev)361 sess_do_free(struct session *s, struct sessev *sev)
362 {
363 struct connection *c;
364
365 while ((c = TAILQ_FIRST(&s->connections)) != NULL)
366 conn_free(c);
367
368 return SESS_FREE;
369 }
370
371 const char *conn_state(int);
372
373
374 int
sess_do_reinstatement(struct session * s,struct sessev * sev)375 sess_do_reinstatement(struct session *s, struct sessev *sev)
376 {
377 struct connection *c, *nc;
378
379 TAILQ_FOREACH_SAFE(c, &s->connections, entry, nc) {
380 log_debug("sess reinstatement[%s]: %s",
381 s->config.SessionName, conn_state(c->state));
382
383 if (c->state & CONN_FAILED) {
384 conn_fsm(c, CONN_EV_FREE);
385 conn_free(c);
386 }
387 }
388
389 return SESS_LOGGED_IN;
390 }
391
392 const char *
sess_state(int s)393 sess_state(int s)
394 {
395 static char buf[15];
396
397 switch (s) {
398 case SESS_INIT:
399 return "INIT";
400 case SESS_FREE:
401 return "FREE";
402 case SESS_LOGGED_IN:
403 return "LOGGED_IN";
404 case SESS_FAILED:
405 return "FAILED";
406 default:
407 snprintf(buf, sizeof(buf), "UKNWN %x", s);
408 return buf;
409 }
410 /* NOTREACHED */
411 }
412
413 const char *
sess_event(enum s_event e)414 sess_event(enum s_event e)
415 {
416 static char buf[15];
417
418 switch (e) {
419 case SESS_EV_START:
420 return "start";
421 case SESS_EV_STOP:
422 return "stop";
423 case SESS_EV_CONN_LOGGED_IN:
424 return "connection logged in";
425 case SESS_EV_CONN_FAIL:
426 return "connection fail";
427 case SESS_EV_CONN_CLOSED:
428 return "connection closed";
429 case SESS_EV_REINSTATEMENT:
430 return "connection reinstated";
431 case SESS_EV_CLOSED:
432 return "session closed";
433 case SESS_EV_TIMEOUT:
434 return "timeout";
435 case SESS_EV_FREE:
436 return "free";
437 case SESS_EV_FAIL:
438 return "fail";
439 }
440
441 snprintf(buf, sizeof(buf), "UKNWN %d", e);
442 return buf;
443 }
444