xref: /openbsd/usr.sbin/iscsid/connection.c (revision 2224f3a1)
1 /*	$OpenBSD: connection.c,v 1.25 2025/01/28 20:41:44 claudio Exp $ */
2 
3 /*
4  * Copyright (c) 2009 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/queue.h>
21 #include <sys/socket.h>
22 #include <sys/uio.h>
23 
24 #include <netinet/in.h>
25 #include <netinet/tcp.h>
26 
27 #include <scsi/iscsi.h>
28 
29 #include <errno.h>
30 #include <event.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 
36 #include "iscsid.h"
37 #include "log.h"
38 
39 void	conn_dispatch(int, short, void *);
40 void	conn_write_dispatch(int, short, void *);
41 
42 int	c_do_connect(struct connection *, enum c_event);
43 int	c_do_login(struct connection *, enum c_event);
44 int	c_do_loggedin(struct connection *, enum c_event);
45 int	c_do_req_logout(struct connection *, enum c_event);
46 int	c_do_logout(struct connection *, enum c_event);
47 int	c_do_loggedout(struct connection *, enum c_event);
48 int	c_do_fail(struct connection *, enum c_event);
49 int	c_do_cleanup(struct connection *, enum c_event);
50 
51 const char *conn_state(int);
52 const char *conn_event(enum c_event);
53 
54 void
conn_new(struct session * s,struct connection_config * cc)55 conn_new(struct session *s, struct connection_config *cc)
56 {
57 	struct connection *c;
58 	int nodelay = 1;
59 
60 	if (!(c = calloc(1, sizeof(*c))))
61 		fatal("session_add_conn");
62 
63 	c->fd = -1;
64 	c->state = CONN_FREE;
65 	c->session = s;
66 	c->cid = arc4random();
67 	c->config = *cc;
68 	c->mine = initiator_conn_defaults;
69 	if (s->config.HeaderDigest != 0)
70 		c->mine.HeaderDigest = s->config.HeaderDigest;
71 	if (s->config.DataDigest != 0)
72 		c->mine.DataDigest = s->config.DataDigest;
73 	c->his = iscsi_conn_defaults;
74 
75 	c->sev.sess = s;
76 	c->sev.conn = c;
77 	evtimer_set(&c->sev.ev, session_fsm_callback, &c->sev);
78 
79 	TAILQ_INIT(&c->pdu_w);
80 	TAILQ_INIT(&c->tasks);
81 	TAILQ_INSERT_TAIL(&s->connections, c, entry);
82 
83 	if (pdu_readbuf_set(&c->prbuf, PDU_READ_SIZE)) {
84 		log_warn("conn_new");
85 		conn_free(c);
86 		return;
87 	}
88 
89 	/* create socket */
90 	c->fd = socket(c->config.TargetAddr.ss_family, SOCK_STREAM, 0);
91 	if (c->fd == -1) {
92 		log_warn("conn_new: socket");
93 		conn_free(c);
94 		return;
95 	}
96 	if (socket_setblockmode(c->fd, 1)) {
97 		log_warn("conn_new: socket_setblockmode");
98 		conn_free(c);
99 		return;
100 	}
101 
102 	/* try to turn off TCP Nagle */
103 	if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &nodelay,
104 	    sizeof(nodelay)) == -1)
105 		log_warn("conn_new: setting TCP_NODELAY");
106 
107 	event_set(&c->ev, c->fd, EV_READ|EV_PERSIST, conn_dispatch, c);
108 	event_set(&c->wev, c->fd, EV_WRITE, conn_write_dispatch, c);
109 
110 	conn_fsm(c, CONN_EV_CONNECT);
111 }
112 
113 void
conn_free(struct connection * c)114 conn_free(struct connection *c)
115 {
116 	log_debug("conn_free");
117 
118 	pdu_readbuf_free(&c->prbuf);
119 	pdu_free_queue(&c->pdu_w);
120 
121 	event_del(&c->sev.ev);
122 	event_del(&c->ev);
123 	event_del(&c->wev);
124 	if (c->fd != -1)
125 		close(c->fd);
126 
127 	taskq_cleanup(&c->tasks);
128 
129 	TAILQ_REMOVE(&c->session->connections, c, entry);
130 	free(c);
131 }
132 
133 void
conn_dispatch(int fd,short event,void * arg)134 conn_dispatch(int fd, short event, void *arg)
135 {
136 	struct connection *c = arg;
137 	ssize_t n;
138 
139 	if (!(event & EV_READ)) {
140 		log_debug("spurious read call");
141 		return;
142 	}
143 	if ((n = pdu_read(c)) == -1) {
144 		if (errno == EAGAIN || errno == ENOBUFS ||
145 		    errno == EINTR)	/* try later */
146 			return;
147 		log_warn("pdu_read");
148 		conn_fsm(c, CONN_EV_FAIL);
149 		return;
150 	}
151 	if (n == 0) {    /* connection closed */
152 		conn_fsm(c, CONN_EV_CLOSED);
153 		return;
154 	}
155 
156 	pdu_parse(c);
157 }
158 
159 void
conn_write_dispatch(int fd,short event,void * arg)160 conn_write_dispatch(int fd, short event, void *arg)
161 {
162 	struct connection *c = arg;
163 	ssize_t n;
164 	int error;
165 	socklen_t len;
166 
167 	if (!(event & EV_WRITE)) {
168 		log_debug("spurious write call");
169 		return;
170 	}
171 
172 	switch (c->state) {
173 	case CONN_XPT_WAIT:
174 		len = sizeof(error);
175 		if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR,
176 		    &error, &len) == -1 || (errno = error)) {
177 			log_warn("connect to %s failed",
178 			    log_sockaddr(&c->config.TargetAddr));
179 			conn_fsm(c, CONN_EV_FAIL);
180 			return;
181 		}
182 		conn_fsm(c, CONN_EV_CONNECTED);
183 		break;
184 	default:
185 		if ((n = pdu_write(c)) == -1) {
186 			log_warn("pdu_write");
187 			conn_fsm(c, CONN_EV_FAIL);
188 			return;
189 		}
190 		if (n == 0) {    /* connection closed */
191 			conn_fsm(c, CONN_EV_CLOSED);
192 			return;
193 		}
194 
195 		/* check if there is more to send */
196 		if (pdu_pending(c))
197 			event_add(&c->wev, NULL);
198 	}
199 }
200 
201 void
conn_fail(struct connection * c)202 conn_fail(struct connection *c)
203 {
204 	log_debug("conn_fail");
205 	conn_fsm(c, CONN_EV_FAIL);
206 }
207 
208 int
conn_task_ready(struct connection * c)209 conn_task_ready(struct connection *c)
210 {
211 	if ((c->state & CONN_RUNNING) && TAILQ_EMPTY(&c->tasks))
212 		return 1;
213 	return 0;
214 }
215 
216 void
conn_task_issue(struct connection * c,struct task * t)217 conn_task_issue(struct connection *c, struct task *t)
218 {
219 	TAILQ_INSERT_TAIL(&c->tasks, t, entry);
220 	conn_task_schedule(c);
221 }
222 
223 void
conn_task_schedule(struct connection * c)224 conn_task_schedule(struct connection *c)
225 {
226 	struct task *t = TAILQ_FIRST(&c->tasks);
227 	struct pdu *p, *np;
228 
229 	if (!t) {
230 		log_debug("conn_task_schedule: task is hiding");
231 		return;
232 	}
233 
234 	/* move pdus to the write queue */
235 	for (p = TAILQ_FIRST(&t->sendq); p != NULL; p = np) {
236 		np = TAILQ_NEXT(p, entry);
237 		TAILQ_REMOVE(&t->sendq, p, entry);
238 		conn_pdu_write(c, p);
239 	}
240 	if (t->callback == NULL) {
241 		/* no callback, immediate command expecting no answer */
242 		conn_task_cleanup(c, t);
243 		free(t);
244 	}
245 }
246 
247 void
conn_task_cleanup(struct connection * c,struct task * t)248 conn_task_cleanup(struct connection *c, struct task *t)
249 {
250 	pdu_free_queue(&t->sendq);
251 	pdu_free_queue(&t->recvq);
252 	/* XXX need some state to know if queued or not */
253 	if (c) {
254 		TAILQ_REMOVE(&c->tasks, t, entry);
255 		if (!TAILQ_EMPTY(&c->tasks))
256 			conn_task_schedule(c);
257 		else
258 			session_schedule(c->session);
259 	}
260 }
261 
262 #define SET_NUM(p, x, v, min, max)				\
263 do {								\
264 	if (!strcmp((p)->key, #v)) {				\
265 		(x)->his.v = text_to_num((p)->value, (min), (max), &err); \
266 		if (err) {					\
267 			log_warnx("bad param %s=%s: %s",	\
268 			    (p)->key, (p)->value, err);		\
269 			errors++;				\
270 		}						\
271 	}							\
272 } while (0)
273 
274 #define SET_BOOL(p, x, v)					\
275 do {								\
276 	if (!strcmp((p)->key, #v)) {				\
277 		(x)->his.v = text_to_bool((p)->value, &err);	\
278 		if (err) {					\
279 			log_warnx("bad param %s=%s: %s",	\
280 			    (p)->key, (p)->value, err);		\
281 			errors++;				\
282 		}						\
283 	}							\
284 } while (0)
285 
286 #define SET_DIGEST(p, x, v)					\
287 do {								\
288 	if (!strcmp((p)->key, #v)) {				\
289 		(x)->his.v = text_to_digest((p)->value, &err);	\
290 		if (err) {					\
291 			log_warnx("bad param %s=%s: %s",	\
292 			    (p)->key, (p)->value, err);		\
293 			errors++;				\
294 		}						\
295 	}							\
296 } while (0)
297 
298 int
conn_parse_kvp(struct connection * c,struct kvp * kvp)299 conn_parse_kvp(struct connection *c, struct kvp *kvp)
300 {
301 	struct kvp *k;
302 	struct session *s = c->session;
303 	const char *err;
304 	int errors = 0;
305 
306 
307 	for (k = kvp; k->key; k++) {
308 log_debug("conn_parse_kvp: %s = %s", k->key, k->value);
309 		/* XXX handle NotUnderstood|Irrelevant|Reject */
310 		SET_NUM(k, s, MaxBurstLength, 512, 16777215);
311 		SET_NUM(k, s, FirstBurstLength, 512, 16777215);
312 		SET_NUM(k, s, DefaultTime2Wait, 0, 3600);
313 		SET_NUM(k, s, DefaultTime2Retain, 0, 3600);
314 		SET_NUM(k, s, MaxOutstandingR2T, 1, 65535);
315 		SET_NUM(k, s, TargetPortalGroupTag, 0, 65535);
316 		SET_NUM(k, s, MaxConnections, 1, 65535);
317 		SET_BOOL(k, s, InitialR2T);
318 		SET_BOOL(k, s, ImmediateData);
319 		SET_BOOL(k, s, DataPDUInOrder);
320 		SET_BOOL(k, s, DataSequenceInOrder);
321 		SET_NUM(k, s, ErrorRecoveryLevel, 0, 2);
322 		SET_NUM(k, c, MaxRecvDataSegmentLength, 512, 16777215);
323 		SET_DIGEST(k, c, HeaderDigest);
324 		SET_DIGEST(k, c, DataDigest);
325 	}
326 
327 	if (errors) {
328 		log_warnx("conn_parse_kvp: errors found");
329 		return -1;
330 	}
331 	return 0;
332 }
333 
334 #undef SET_NUM
335 #undef SET_BOOL
336 #undef SET_DIGEST
337 
338 #define GET_BOOL_P(dst, req, k, src, f)				\
339 do {								\
340 	if (f == 0 && !strcmp(req, #k)) {			\
341 	(dst)->key = #k;					\
342 	(dst)->value = ((src)->mine.k) ? "Yes" : "No";		\
343 	f++;							\
344 	}							\
345 } while (0)
346 
347 #define GET_DIGEST_P(dst, req, k, src, f)				\
348 do {									\
349 	if (f == 0 && !strcmp(req, #k)) {				\
350 		(dst)->key = #k;					\
351 		(dst)->value =						\
352 		    ((src)->mine.k == DIGEST_NONE) ? "None" : "CRC32C,None";\
353 		f++;							\
354 	}								\
355 } while (0)
356 
357 #define GET_NUM_P(dst, req, k, src, f, e)				\
358 do {									\
359 	if (f == 0 && !strcmp(req, #k)) {				\
360 		(dst)->key = #k;					\
361 		if (asprintf(&((dst)->value), "%u", (src)->mine.k) == -1)\
362 			e++;						\
363 		else							\
364 			(dst)->flags |= KVP_VALUE_ALLOCED;		\
365 		f++;							\
366 	}								\
367 } while (0)
368 
369 #define GET_STR_C(dst, req, k, src, f)				\
370 do {								\
371 	if (f == 0 && !strcmp(req, #k)) {			\
372 		(dst)->key = #k;				\
373 		(dst)->value = (src)->config.k;			\
374 		f++;						\
375 	}							\
376 } while (0)
377 
378 #define GET_STYPE_C(dst, req, k, src, f)				\
379 do {									\
380 	if (f == 0 && !strcmp(req, #k)) {				\
381 		(dst)->key = #k;					\
382 		(dst)->value = ((src)->config.k == SESSION_TYPE_DISCOVERY)\
383 		    ? "Discovery" : "Normal";				\
384 		f++;							\
385 	}								\
386 } while (0)
387 
388 int
kvp_set_from_mine(struct kvp * kvp,const char * key,struct connection * c)389 kvp_set_from_mine(struct kvp *kvp, const char *key, struct connection *c)
390 {
391 	int e = 0, f = 0;
392 
393 	if (kvp->flags & KVP_KEY_ALLOCED)
394 		free(kvp->key);
395 	kvp->key = NULL;
396 	if (kvp->flags & KVP_VALUE_ALLOCED)
397 		free(kvp->value);
398 	kvp->value = NULL;
399 	kvp->flags = 0;
400 
401 	/* XXX handle at least CHAP */
402 	if (!strcmp(key, "AuthMethod")) {
403 		kvp->key = "AuthMethod";
404 		kvp->value = "None";
405 		return 0;
406 	}
407 	GET_DIGEST_P(kvp, key, HeaderDigest, c, f);
408 	GET_DIGEST_P(kvp, key, DataDigest, c, f);
409 	GET_NUM_P(kvp, key, MaxConnections, c->session, f, e);
410 	GET_STR_C(kvp, key, TargetName, c->session, f);
411 	GET_STR_C(kvp, key, InitiatorName, c->session, f);
412 	GET_BOOL_P(kvp, key, InitialR2T, c->session, f);
413 	GET_BOOL_P(kvp, key, ImmediateData, c->session, f);
414 	GET_NUM_P(kvp, key, MaxRecvDataSegmentLength, c, f, e);
415 	GET_NUM_P(kvp, key, MaxBurstLength, c->session, f, e);
416 	GET_NUM_P(kvp, key, FirstBurstLength, c->session, f, e);
417 	GET_NUM_P(kvp, key, DefaultTime2Wait, c->session, f, e);
418 	GET_NUM_P(kvp, key, DefaultTime2Retain, c->session, f, e);
419 	GET_NUM_P(kvp, key, MaxOutstandingR2T, c->session, f, e);
420 	GET_BOOL_P(kvp, key, DataPDUInOrder, c->session, f);
421 	GET_BOOL_P(kvp, key, DataSequenceInOrder, c->session, f);
422 	GET_NUM_P(kvp, key, ErrorRecoveryLevel, c->session, f, e);
423 	GET_STYPE_C(kvp, key, SessionType, c->session, f);
424 	/* XXX handle TaskReporting */
425 
426 	if (f == 0) {
427 		errno = EINVAL;
428 		return 1;
429 	}
430 
431 	return e;
432 }
433 
434 #undef GET_BOOL_P
435 #undef GET_DIGEST_P
436 #undef GET_NUM_P
437 #undef GET_STR_C
438 #undef GET_STYPE_C
439 
440 void
conn_pdu_write(struct connection * c,struct pdu * p)441 conn_pdu_write(struct connection *c, struct pdu *p)
442 {
443 	struct iscsi_pdu *ipdu;
444 
445 /* XXX I GUESS THIS SHOULD BE MOVED TO PDU SOMEHOW... */
446 	ipdu = pdu_getbuf(p, NULL, PDU_HEADER);
447 	switch (ISCSI_PDU_OPCODE(ipdu->opcode)) {
448 	case ISCSI_OP_I_NOP:
449 	case ISCSI_OP_SCSI_REQUEST:
450 	case ISCSI_OP_TASK_REQUEST:
451 	case ISCSI_OP_LOGIN_REQUEST:
452 	case ISCSI_OP_TEXT_REQUEST:
453 	case ISCSI_OP_DATA_OUT:
454 	case ISCSI_OP_LOGOUT_REQUEST:
455 	case ISCSI_OP_SNACK_REQUEST:
456 		ipdu->expstatsn = ntohl(c->expstatsn);
457 		break;
458 	}
459 
460 	TAILQ_INSERT_TAIL(&c->pdu_w, p, entry);
461 	event_add(&c->wev, NULL);
462 }
463 
464 /* connection state machine more or less as specified in the RFC */
465 struct {
466 	int		state;
467 	enum c_event	event;
468 	int		(*action)(struct connection *, enum c_event);
469 } fsm[] = {
470 	{ CONN_FREE, CONN_EV_CONNECT, c_do_connect },		/* T1 */
471 	{ CONN_XPT_WAIT, CONN_EV_CONNECTED, c_do_login },	/* T4 */
472 	{ CONN_IN_LOGIN, CONN_EV_LOGGED_IN, c_do_loggedin },	/* T5 */
473 	{ CONN_LOGGED_IN, CONN_EV_LOGOUT, c_do_logout },	/* T9 */
474 	{ CONN_LOGGED_IN, CONN_EV_REQ_LOGOUT, c_do_req_logout },/* T11 */
475 	{ CONN_LOGOUT_REQ, CONN_EV_LOGOUT, c_do_logout },	/* T10 */
476 	{ CONN_LOGOUT_REQ, CONN_EV_REQ_LOGOUT, c_do_req_logout},/* T12 */
477 	{ CONN_LOGOUT_REQ, CONN_EV_LOGGED_OUT, c_do_loggedout },/* T18 */
478 	{ CONN_IN_LOGOUT, CONN_EV_LOGGED_OUT, c_do_loggedout },	/* T13 */
479 	{ CONN_IN_LOGOUT, CONN_EV_REQ_LOGOUT, c_do_req_logout },/* T14 */
480 	{ CONN_CLEANUP_WAIT, CONN_EV_CLEANING_UP, c_do_cleanup},/* M2 */
481 	{ CONN_CLEANUP_WAIT, CONN_EV_FREE, c_do_loggedout },	/* M1 */
482 	{ CONN_IN_CLEANUP, CONN_EV_FREE, c_do_loggedout },	/* M4 */
483 	{ CONN_IN_CLEANUP, CONN_EV_CLEANING_UP, c_do_cleanup},
484 	/* either one of T2, T7, T15, T16, T17, M3 */
485 	{ CONN_ANYSTATE, CONN_EV_CLOSED, c_do_fail },
486 	{ CONN_ANYSTATE, CONN_EV_FAIL, c_do_fail },
487 	{ CONN_ANYSTATE, CONN_EV_FREE, c_do_fail },
488 	{ 0, 0, NULL }
489 };
490 
491 void
conn_fsm(struct connection * c,enum c_event event)492 conn_fsm(struct connection *c, enum c_event event)
493 {
494 	int	i, ns;
495 
496 	for (i = 0; fsm[i].action != NULL; i++) {
497 		if (c->state & fsm[i].state && event == fsm[i].event) {
498 			log_debug("conn_fsm[%s]: %s ev %s",
499 			    c->session->config.SessionName,
500 			    conn_state(c->state), conn_event(event));
501 			ns = fsm[i].action(c, event);
502 			if (ns == -1)
503 				/* XXX better please */
504 				fatalx("conn_fsm: action failed");
505 			log_debug("conn_fsm[%s]: new state %s",
506 			    c->session->config.SessionName, conn_state(ns));
507 			c->state = ns;
508 			return;
509 		}
510 	}
511 	log_warnx("conn_fsm[%s]: unhandled state transition [%s, %s]",
512 	    c->session->config.SessionName, conn_state(c->state),
513 	    conn_event(event));
514 	fatalx("bork bork bork");
515 }
516 
517 int
c_do_connect(struct connection * c,enum c_event ev)518 c_do_connect(struct connection *c, enum c_event ev)
519 {
520 	if (c->fd == -1) {
521 		log_warnx("connect(%s), lost socket",
522 		    log_sockaddr(&c->config.TargetAddr));
523 		session_fsm(&c->sev, SESS_EV_CONN_FAIL, 0);
524 		return CONN_FREE;
525 	}
526 	if (c->config.LocalAddr.ss_len != 0) {
527 		if (bind(c->fd, (struct sockaddr *)&c->config.LocalAddr,
528 		    c->config.LocalAddr.ss_len) == -1) {
529 			log_warn("bind(%s)",
530 			    log_sockaddr(&c->config.LocalAddr));
531 			session_fsm(&c->sev, SESS_EV_CONN_FAIL, 0);
532 			return CONN_FREE;
533 		}
534 	}
535 	if (connect(c->fd, (struct sockaddr *)&c->config.TargetAddr,
536 	    c->config.TargetAddr.ss_len) == -1) {
537 		if (errno == EINPROGRESS) {
538 			event_add(&c->wev, NULL);
539 			event_add(&c->ev, NULL);
540 			return CONN_XPT_WAIT;
541 		} else {
542 			log_warn("connect(%s)",
543 			    log_sockaddr(&c->config.TargetAddr));
544 			session_fsm(&c->sev, SESS_EV_CONN_FAIL, 0);
545 			return CONN_FREE;
546 		}
547 	}
548 	event_add(&c->ev, NULL);
549 	/* move forward */
550 	return c_do_login(c, CONN_EV_CONNECTED);
551 }
552 
553 int
c_do_login(struct connection * c,enum c_event ev)554 c_do_login(struct connection *c, enum c_event ev)
555 {
556 	/* start a login session and hope for the best ... */
557 	initiator_login(c);
558 	return CONN_IN_LOGIN;
559 }
560 
561 int
c_do_loggedin(struct connection * c,enum c_event ev)562 c_do_loggedin(struct connection *c, enum c_event ev)
563 {
564 	iscsi_merge_conn_params(&c->active, &c->mine, &c->his);
565 	session_fsm(&c->sev, SESS_EV_CONN_LOGGED_IN, 0);
566 
567 	return CONN_LOGGED_IN;
568 }
569 
570 int
c_do_req_logout(struct connection * c,enum c_event ev)571 c_do_req_logout(struct connection *c, enum c_event ev)
572 {
573 	/* target requested logout. XXX implement async handler */
574 
575 	if (c->state & CONN_IN_LOGOUT)
576 		return CONN_IN_LOGOUT;
577 	else
578 		return CONN_LOGOUT_REQ;
579 }
580 
581 int
c_do_logout(struct connection * c,enum c_event ev)582 c_do_logout(struct connection *c, enum c_event ev)
583 {
584 	/* logout is in progress ... */
585 	return CONN_IN_LOGOUT;
586 }
587 
588 int
c_do_loggedout(struct connection * c,enum c_event ev)589 c_do_loggedout(struct connection *c, enum c_event ev)
590 {
591 	/*
592 	 * Called by the session fsm before calling conn_free.
593 	 * Doing this so the state transition is logged.
594 	 */
595 	return CONN_FREE;
596 }
597 
598 int
c_do_fail(struct connection * c,enum c_event ev)599 c_do_fail(struct connection *c, enum c_event ev)
600 {
601 	log_debug("c_do_fail");
602 
603 	/* cleanup events so that the connection does not retrigger */
604 	event_del(&c->ev);
605 	event_del(&c->wev);
606 	close(c->fd);
607 	c->fd = -1;	/* make sure this fd is not closed again */
608 
609 	/* all pending task have failed so clean them up */
610 	taskq_cleanup(&c->tasks);
611 
612 	/* session will take care of cleaning up the mess */
613 	session_fsm(&c->sev, SESS_EV_CONN_FAIL, 0);
614 
615 	if (ev == CONN_EV_FREE || c->state & CONN_NEVER_LOGGED_IN)
616 		return CONN_FREE;
617 	return CONN_CLEANUP_WAIT;
618 }
619 
620 int
c_do_cleanup(struct connection * c,enum c_event ev)621 c_do_cleanup(struct connection *c, enum c_event ev)
622 {
623 	/* nothing to do here just adjust state */
624 	return CONN_IN_CLEANUP;
625 }
626 
627 const char *
conn_state(int s)628 conn_state(int s)
629 {
630 	static char buf[15];
631 
632 	switch (s) {
633 	case CONN_FREE:
634 		return "FREE";
635 	case CONN_XPT_WAIT:
636 		return "XPT_WAIT";
637 	case CONN_XPT_UP:
638 		return "XPT_UP";
639 	case CONN_IN_LOGIN:
640 		return "IN_LOGIN";
641 	case CONN_LOGGED_IN:
642 		return "LOGGED_IN";
643 	case CONN_IN_LOGOUT:
644 		return "IN_LOGOUT";
645 	case CONN_LOGOUT_REQ:
646 		return "LOGOUT_REQ";
647 	case CONN_CLEANUP_WAIT:
648 		return "CLEANUP_WAIT";
649 	case CONN_IN_CLEANUP:
650 		return "IN_CLEANUP";
651 	default:
652 		snprintf(buf, sizeof(buf), "UKNWN %x", s);
653 		return buf;
654 	}
655 	/* NOTREACHED */
656 }
657 
658 const char *
conn_event(enum c_event e)659 conn_event(enum c_event e)
660 {
661 	static char buf[15];
662 
663 	switch (e) {
664 	case CONN_EV_FAIL:
665 		return "fail";
666 	case CONN_EV_CONNECT:
667 		return "connect";
668 	case CONN_EV_CONNECTED:
669 		return "connected";
670 	case CONN_EV_LOGGED_IN:
671 		return "logged in";
672 	case CONN_EV_REQ_LOGOUT:
673 		return "logout requested";
674 	case CONN_EV_LOGOUT:
675 		return "logout";
676 	case CONN_EV_LOGGED_OUT:
677 		return "logged out";
678 	case CONN_EV_CLEANING_UP:
679 		return "cleaning up";
680 	case CONN_EV_CLOSED:
681 		return "closed";
682 	case CONN_EV_FREE:
683 		return "forced free";
684 	}
685 
686 	snprintf(buf, sizeof(buf), "UKNWN %d", e);
687 	return buf;
688 }
689