xref: /openbsd/usr.sbin/iscsid/connection.c (revision e1c3b4f8)
1 /*	$OpenBSD: connection.c,v 1.11 2011/04/28 18:32:01 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/ioctl.h>
21 #include <sys/queue.h>
22 #include <sys/socket.h>
23 #include <sys/uio.h>
24 
25 #include <netinet/in.h>
26 #include <netinet/tcp.h>
27 
28 #include <scsi/iscsi.h>
29 #include <scsi/scsi_all.h>
30 #include <dev/vscsivar.h>
31 
32 #include <errno.h>
33 #include <event.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 
38 #include "iscsid.h"
39 #include "log.h"
40 
41 void	conn_dispatch(int, short, void *);
42 void	conn_write_dispatch(int, short, void *);
43 
44 int	c_do_connect(struct connection *, enum c_event);
45 int	c_do_login(struct connection *, enum c_event);
46 int	c_do_loggedin(struct connection *, enum c_event);
47 int	c_do_logout(struct connection *, enum c_event);
48 int	c_do_loggedout(struct connection *, enum c_event);
49 int	c_do_fail(struct connection *, enum c_event);
50 
51 const char *conn_state(int);
52 const char *conn_event(enum c_event);
53 
54 void
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 	TAILQ_INIT(&c->pdu_w);
69 	TAILQ_INIT(&c->tasks);
70 	TAILQ_INSERT_TAIL(&s->connections, c, entry);
71 
72 	if (pdu_readbuf_set(&c->prbuf, PDU_READ_SIZE)) {
73 		log_warn("conn_new");
74 		conn_free(c);
75 		return;
76 	}
77 
78 	/* create socket */
79 	c->fd = socket(c->config.TargetAddr.ss_family, SOCK_STREAM, 0);
80 	if (c->fd == -1) {
81 		log_warn("conn_new: socket");
82 		conn_free(c);
83 		return;
84 	}
85 	if (socket_setblockmode(c->fd, 1)) {
86 		log_warn("conn_new: socket_setblockmode");
87 		conn_free(c);
88 		return;
89 	}
90 
91 	/* try to turn off TCP Nagle */
92 	if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &nodelay,
93 	    sizeof(nodelay)) == -1)
94 		log_warn("conn_new: setting TCP_NODELAY");
95 
96 	event_set(&c->ev, c->fd, EV_READ|EV_PERSIST, conn_dispatch, c);
97 	event_set(&c->wev, c->fd, EV_WRITE, conn_write_dispatch, c);
98 	event_add(&c->ev, NULL);
99 
100 	conn_fsm(c, CONN_EV_CONNECT);
101 }
102 
103 void
104 conn_free(struct connection *c)
105 {
106 	pdu_readbuf_free(&c->prbuf);
107 	pdu_free_queue(&c->pdu_w);
108 
109 	event_del(&c->ev);
110 	event_del(&c->wev);
111 	close(c->fd);
112 
113 	taskq_cleanup(&c->tasks);
114 
115 	TAILQ_REMOVE(&c->session->connections, c, entry);
116 	free(c);
117 }
118 
119 void
120 conn_dispatch(int fd, short event, void *arg)
121 {
122 	struct connection *c = arg;
123 	ssize_t n;
124 
125 	if (!(event & EV_READ)) {
126 		log_debug("spurious read call");
127 		return;
128 	}
129 	if ((n = pdu_read(c)) == -1) {
130 		conn_fsm(c, CONN_EV_FAIL);
131 		return;
132 	}
133 	if (n == 0) {    /* connection closed */
134 		conn_fsm(c, CONN_EV_CLOSED);
135 		return;
136 	}
137 
138 	pdu_parse(c);
139 }
140 
141 void
142 conn_write_dispatch(int fd, short event, void *arg)
143 {
144 	struct connection *c = arg;
145 	ssize_t n;
146 	int error;
147 	socklen_t len;
148 
149 	if (!(event & EV_WRITE)) {
150 		log_debug("spurious write call");
151 		return;
152 	}
153 
154 	switch (c->state) {
155 	case CONN_XPT_WAIT:
156 		len = sizeof(error);
157 		if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR,
158 		    &error, &len) == -1 || (errno = error)) {
159 			log_warn("cwd connect(%s)",
160 			    log_sockaddr(&c->config.TargetAddr));
161 			conn_fsm(c, CONN_EV_FAIL);
162 			return;
163 		}
164 		conn_fsm(c, CONN_EV_CONNECTED);
165 		break;
166 	default:
167 		if ((n = pdu_write(c)) == -1) {
168 			log_warn("pdu_write");
169 			conn_fsm(c, CONN_EV_FAIL);
170 			return;
171 		}
172 		if (n == 0) {    /* connection closed */
173 			conn_fsm(c, CONN_EV_CLOSED);
174 			return;
175 		}
176 
177 		/* check if there is more to send */
178 		if (pdu_pending(c))
179 			event_add(&c->wev, NULL);
180 	}
181 }
182 
183 void
184 conn_logout(struct connection *c)
185 {
186 	conn_fsm(c, CONN_EV_LOGOUT);
187 }
188 
189 void
190 conn_fail(struct connection *c)
191 {
192 	log_debug("conn_fail");
193 	conn_fsm(c, CONN_EV_FAIL);
194 }
195 
196 void
197 conn_loggedin(struct connection *c)
198 {
199 	if (c->session->config.SessionType == SESSION_TYPE_DISCOVERY)
200 		conn_fsm(c, CONN_EV_DISCOVERY);
201 	else
202 		conn_fsm(c, CONN_EV_LOGGED_IN);
203 }
204 
205 int
206 conn_task_ready(struct connection *c)
207 {
208 	if ((c->state & CONN_LOGGED_IN) && TAILQ_EMPTY(&c->tasks))
209 		return 1;
210 	return 0;
211 }
212 
213 void
214 conn_task_issue(struct connection *c, struct task *t)
215 {
216 	TAILQ_INSERT_TAIL(&c->tasks, t, entry);
217 	conn_task_schedule(c);
218 }
219 
220 void
221 conn_task_schedule(struct connection *c)
222 {
223 	struct task *t = TAILQ_FIRST(&c->tasks);
224 	struct pdu *p, *np;
225 
226 	if (!t) {
227 		log_debug("conn_task_schedule: task is hiding");
228 		return;
229 	}
230 
231 	/* move pdus to the write queue */
232 	for (p = TAILQ_FIRST(&t->sendq); p != NULL; p = np) {
233 		np = TAILQ_NEXT(p, entry);
234 		TAILQ_REMOVE(&t->sendq, p, entry);
235 		conn_pdu_write(c, p);
236 	}
237 	if (t->callback == NULL) {
238 		/* no callback, immediate command expecting no answer */
239 		conn_task_cleanup(c, t);
240 		free(t);
241 	}
242 }
243 
244 void
245 conn_task_cleanup(struct connection *c, struct task *t)
246 {
247 /* XXX THIS FEELS WRONG FOR NOW */
248 	pdu_free_queue(&t->sendq);
249 	pdu_free_queue(&t->recvq);
250 	/* XXX need some state to know if queued or not */
251 	if (c) {
252 		TAILQ_REMOVE(&c->tasks, t, entry);
253 		if (!TAILQ_EMPTY(&c->tasks))
254 			conn_task_schedule(c);
255 		else
256 			session_schedule(c->session);
257 	}
258 }
259 
260 
261 void
262 conn_pdu_write(struct connection *c, struct pdu *p)
263 {
264 	struct iscsi_pdu *ipdu;
265 
266 /* XXX I GUESS THIS SHOULD BE MOVED TO PDU SOMEHOW... */
267 	ipdu = pdu_getbuf(p, NULL, PDU_HEADER);
268 	switch (ISCSI_PDU_OPCODE(ipdu->opcode)) {
269 	case ISCSI_OP_I_NOP:
270 	case ISCSI_OP_SCSI_REQUEST:
271 	case ISCSI_OP_TASK_REQUEST:
272 	case ISCSI_OP_LOGIN_REQUEST:
273 	case ISCSI_OP_TEXT_REQUEST:
274 	case ISCSI_OP_DATA_OUT:
275 	case ISCSI_OP_LOGOUT_REQUEST:
276 	case ISCSI_OP_SNACK_REQUEST:
277 		ipdu->expstatsn = ntohl(c->expstatsn);
278 		break;
279 	}
280 
281 	TAILQ_INSERT_TAIL(&c->pdu_w, p, entry);
282 	event_add(&c->wev, NULL);
283 }
284 
285 /* connection state machine more or less as specified in the RFC */
286 struct {
287 	int		state;
288 	enum c_event	event;
289 	int		(*action)(struct connection *, enum c_event);
290 } fsm[] = {
291 	{ CONN_FREE, CONN_EV_CONNECT, c_do_connect },		/* T1 */
292 	{ CONN_XPT_WAIT, CONN_EV_CONNECTED, c_do_login },	/* T4 */
293 	{ CONN_IN_LOGIN, CONN_EV_LOGGED_IN, c_do_loggedin },	/* T5 */
294 	{ CONN_IN_LOGIN, CONN_EV_DISCOVERY, c_do_loggedin },	/* T5 */
295 	{ CONN_LOGGED_IN, CONN_EV_LOGOUT, c_do_logout },	/* T9 */
296 	{ CONN_LOGOUT_REQ, CONN_EV_LOGOUT, c_do_logout },	/* T10 */
297 	{ CONN_IN_LOGOUT, CONN_EV_LOGGED_OUT, c_do_loggedout },	/* T13 */
298 	{ CONN_ANYSTATE, CONN_EV_CLOSED, c_do_fail },
299 	{ CONN_ANYSTATE, CONN_EV_FAIL, c_do_fail },
300 	{ 0, 0, NULL }
301 };
302 
303 void
304 conn_fsm(struct connection *c, enum c_event event)
305 {
306 	int	i, ns;
307 
308 	for (i = 0; fsm[i].action != NULL; i++) {
309 		if (c->state & fsm[i].state && event == fsm[i].event) {
310 			log_debug("conn_fsm[%s]: %s ev %s",
311 			    c->session->config.SessionName,
312 			    conn_state(c->state), conn_event(event));
313 			ns = fsm[i].action(c, event);
314 			if (ns == -1)
315 				/* XXX better please */
316 				fatalx("conn_fsm: action failed");
317 			log_debug("conn_fsm[%s]: new state %s",
318 			    c->session->config.SessionName, conn_state(ns));
319 			c->state = ns;
320 			return;
321 		}
322 	}
323 	log_warnx("conn_fsm[%s]: unhandled state transition [%s, %s]",
324 	    c->session->config.SessionName, conn_state(c->state),
325 	    conn_event(event));
326 	fatalx("bork bork bork");
327 }
328 
329 int
330 c_do_connect(struct connection *c, enum c_event ev)
331 {
332 	if (c->fd == -1) {
333 		log_warnx("connect(%s), lost socket",
334 		    log_sockaddr(&c->config.TargetAddr));
335 		session_fsm(c->session, SESS_EV_CONN_FAIL, c);
336 		return (CONN_FREE);
337 	}
338 
339 	if (connect(c->fd, (struct sockaddr *)&c->config.TargetAddr,
340 	    c->config.TargetAddr.ss_len) == -1) {
341 		if (errno == EINPROGRESS) {
342 			event_add(&c->wev, NULL);
343 			return (CONN_XPT_WAIT);
344 		} else {
345 			log_warn("connect(%s)",
346 			    log_sockaddr(&c->config.TargetAddr));
347 			session_fsm(c->session, SESS_EV_CONN_FAIL, c);
348 			return (CONN_FREE);
349 		}
350 	}
351 	/* move forward */
352 	return (c_do_login(c, CONN_EV_CONNECTED));
353 }
354 
355 int
356 c_do_login(struct connection *c, enum c_event ev)
357 {
358 	/* start a login session and hope for the best ... */
359 	initiator_login(c);
360 	return (CONN_IN_LOGIN);
361 }
362 
363 int
364 c_do_loggedin(struct connection *c, enum c_event ev)
365 {
366 	if (ev == CONN_EV_LOGGED_IN)
367 		vscsi_event(VSCSI_REQPROBE, c->session->target, -1);
368 	else
369 		initiator_discovery(c->session);
370 	return (CONN_LOGGED_IN);
371 }
372 
373 int
374 c_do_logout(struct connection *c, enum c_event ev)
375 {
376 	/* do full logout */
377 	initiator_logout(c, ISCSI_LOGOUT_CLOSE_SESS, 1);
378 	return (CONN_IN_LOGOUT);
379 }
380 
381 int
382 c_do_loggedout(struct connection *c, enum c_event ev)
383 {
384 	/* close TCP session and cleanup */
385 	event_del(&c->ev);
386 	event_del(&c->wev);
387 	close(c->fd);
388 
389 	session_fsm(c->session, SESS_EV_CONN_CLOSED, c);
390 
391 	return (CONN_FREE);
392 }
393 
394 int
395 c_do_fail(struct connection *c, enum c_event ev)
396 {
397 	/* cleanup events so that the connection does not retrigger */
398 	event_del(&c->ev);
399 	event_del(&c->wev);
400 	close(c->fd);
401 
402 	session_fsm(c->session, SESS_EV_CONN_FAIL, c);
403 
404 	if (c->state & CONN_NOT_LOGGED_IN)
405 		return (CONN_FREE);
406 	return (CONN_CLEANUP_WAIT);
407 }
408 
409 
410 const char *
411 conn_state(int s)
412 {
413 	static char buf[15];
414 
415 	switch (s) {
416 	case CONN_FREE:
417 		return "FREE";
418 	case CONN_XPT_WAIT:
419 		return "XPT_WAIT";
420 	case CONN_XPT_UP:
421 		return "XPT_UP";
422 	case CONN_IN_LOGIN:
423 		return "IN_LOGIN";
424 	case CONN_LOGGED_IN:
425 		return "LOGGED_IN";
426 	case CONN_IN_LOGOUT:
427 		return "IN_LOGOUT";
428 	case CONN_LOGOUT_REQ:
429 		return "LOGOUT_REQ";
430 	case CONN_CLEANUP_WAIT:
431 		return "CLEANUP_WAIT";
432 	case CONN_IN_CLEANUP:
433 		return "IN_CLEANUP";
434 	default:
435 		snprintf(buf, sizeof(buf), "UKNWN %x", s);
436 		return buf;
437 	}
438 	/* NOTREACHED */
439 }
440 
441 const char *
442 conn_event(enum c_event e)
443 {
444 	static char buf[15];
445 
446 	switch (e) {
447 	case CONN_EV_FAIL:
448 		return "fail";
449 	case CONN_EV_CONNECT:
450 		return "connect";
451 	case CONN_EV_CONNECTED:
452 		return "connected";
453 	case CONN_EV_LOGGED_IN:
454 		return "logged in";
455 	case CONN_EV_DISCOVERY:
456 		return "discovery";
457 	case CONN_EV_LOGOUT:
458 		return "logout";
459 	case CONN_EV_LOGGED_OUT:
460 		return "logged out";
461 	case CONN_EV_CLOSED:
462 		return "closed";
463 	}
464 
465 	snprintf(buf, sizeof(buf), "UKNWN %d", e);
466 	return buf;
467 }
468