xref: /openbsd/usr.sbin/iscsid/connection.c (revision bde1ae23)
1 /*	$OpenBSD: connection.c,v 1.1 2010/09/24 09:43:19 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 
27 #include <scsi/iscsi.h>
28 #include <scsi/scsi_all.h>
29 #include <dev/vscsivar.h>
30 
31 #include <errno.h>
32 #include <event.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 
37 #include "iscsid.h"
38 #include "log.h"
39 
40 void	conn_fsm(struct connection *, enum c_event);
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 
49 const char *conn_state(int);
50 const char *conn_event(enum c_event);
51 
52 void
53 conn_new(struct session *s, struct connection_config *cc)
54 {
55 	struct connection *c;
56 
57 	if (!(c = calloc(1, sizeof(*c))))
58 		fatal("session_add_conn");
59 
60 	c->fd = -1;
61 	c->state = CONN_FREE;
62 	c->session = s;
63 	c->cid = arc4random();
64 	c->config = *cc;
65 	TAILQ_INIT(&c->pdu_w);
66 	TAILQ_INIT(&c->tasks);
67 	TAILQ_INSERT_TAIL(&s->connections, c, entry);
68 
69 	if (pdu_readbuf_set(&c->prbuf, PDU_READ_SIZE)) {
70 		log_warn("conn_new");
71 		conn_free(c);
72 		return;
73 	}
74 
75 	/* create socket */
76 	c->fd = socket(c->config.TargetAddr.ss_family, SOCK_STREAM, 0);
77 	if (c->fd == -1) {
78 		log_warn("conn_new: socket");
79 		conn_free(c);
80 		return;
81 	}
82 	if (socket_setblockmode(c->fd, 1)) {
83 		log_warn("conn_new: socket_setblockmode");
84 		conn_free(c);
85 		return;
86 	}
87 
88 	event_set(&c->ev, c->fd, EV_READ|EV_PERSIST, conn_dispatch, c);
89 	event_set(&c->wev, c->fd, EV_WRITE, conn_write_dispatch, c);
90 	event_add(&c->ev, NULL);
91 
92 	conn_fsm(c, CONN_EV_CONNECT);
93 }
94 
95 void
96 conn_free(struct connection *c)
97 {
98 	pdu_readbuf_free(&c->prbuf);
99 	pdu_free_queue(&c->pdu_w);
100 
101 	event_del(&c->ev);
102 	event_del(&c->wev);
103 	close(c->fd);
104 
105 	TAILQ_REMOVE(&c->session->connections, c, entry);
106 	free(c);
107 }
108 
109 void
110 conn_dispatch(int fd, short event, void *arg)
111 {
112 	struct connection *c = arg;
113 	ssize_t n;
114 
115 	if (!(event & EV_READ)) {
116 		log_debug("spurious read call");
117 		return;
118 	}
119 	if ((n = pdu_read(c)) == -1) {
120 		conn_fail(c);
121 		return;
122 	}
123 	if (n == 0) {    /* connection closed */
124 		conn_fsm(c, CONN_EV_CLOSE);
125 		return;
126 	}
127 
128 	pdu_parse(c);
129 }
130 
131 void
132 conn_write_dispatch(int fd, short event, void *arg)
133 {
134 	struct connection *c = arg;
135 	ssize_t n;
136 	int error;
137 	socklen_t len;
138 
139 	if (!(event & EV_WRITE)) {
140 		log_debug("spurious write call");
141 		return;
142 	}
143 
144 	switch (c->state) {
145 	case CONN_XPT_WAIT:
146 		len = sizeof(error);
147 		if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR,
148 		    &error, &len) == -1 || (errno = error)) {
149 			log_warn("cwd connect(%s)",
150 			    log_sockaddr(&c->config.TargetAddr));
151 			conn_fail(c);
152 			return;
153 		}
154 		conn_fsm(c, CONN_EV_CONNECTED);
155 		break;
156 	default:
157 		if ((n = pdu_write(c)) == -1) {
158 			conn_fail(c);
159 			return;
160 		}
161 		if (n == 0) {    /* connection closed */
162 			conn_fsm(c, CONN_EV_CLOSE);
163 			return;
164 		}
165 
166 		/* check if there is more to send */
167 		if (pdu_pending(c))
168 			event_add(&c->wev, NULL);
169 	}
170 }
171 
172 void
173 conn_close(struct connection *c)
174 {
175 	conn_fsm(c, CONN_EV_CLOSE);
176 }
177 
178 void
179 conn_fail(struct connection *c)
180 {
181 	conn_fsm(c, CONN_EV_FAIL);
182 }
183 
184 void
185 conn_loggedin(struct connection *c)
186 {
187 	if (c->session->config.SessionType == SESSION_TYPE_DISCOVERY)
188 		conn_fsm(c, CONN_EV_DISCOVERY);
189 	else
190 		conn_fsm(c, CONN_EV_LOGGED_IN);
191 }
192 
193 int
194 conn_task_issue(struct connection *c, struct task *t)
195 {
196 	/* XXX need to verify that we're in the right state for the task */
197 
198 log_debug("conn_task_issue");
199 	if (!TAILQ_EMPTY(&c->tasks))
200 		return 0;
201 
202 	TAILQ_INSERT_TAIL(&c->tasks, t, entry);
203 	conn_task_schedule(c);
204 	return 1;
205 }
206 
207 void
208 conn_task_schedule(struct connection *c)
209 {
210 	struct task *t = TAILQ_FIRST(&c->tasks);
211 	struct pdu *p, *np;
212 
213 	if (!t) {
214 		log_debug("conn_task_schedule: task is hiding");
215 		return;
216 	}
217 
218 	/* move pdus to the write queue */
219 	for (p = TAILQ_FIRST(&t->sendq); p != NULL; p = np) {
220 		np = TAILQ_NEXT(p, entry);
221 		TAILQ_REMOVE(&t->sendq, p, entry);
222 		conn_pdu_write(c, p);	/* maybe inline ? */
223 	}
224 }
225 
226 void
227 conn_pdu_write(struct connection *c, struct pdu *p)
228 {
229 	struct iscsi_pdu *ipdu;
230 
231 /* XXX I GUESS THIS SHOULD BE MOVED TO PDU SOMEHOW... */
232 	ipdu = pdu_getbuf(p, NULL, PDU_HEADER);
233 	switch (ISCSI_PDU_OPCODE(ipdu->opcode)) {
234 	case ISCSI_OP_TASK_REQUEST:
235 		ipdu->expstatsn = ntohl(c->expstatsn);
236 		break;
237 	}
238 
239 	TAILQ_INSERT_TAIL(&c->pdu_w, p, entry);
240 	event_add(&c->wev, NULL);
241 }
242 
243 /* connection state machine more or less as specified in the RFC */
244 struct {
245 	int		state;
246 	enum c_event	event;
247 	int		(*action)(struct connection *, enum c_event);
248 } fsm[] = {
249 	{ CONN_FREE, CONN_EV_CONNECT, c_do_connect },
250 	{ CONN_XPT_WAIT, CONN_EV_CONNECTED, c_do_login },
251 	{ CONN_IN_LOGIN, CONN_EV_LOGGED_IN, c_do_loggedin },
252 	{ CONN_IN_LOGIN, CONN_EV_DISCOVERY, c_do_loggedin },
253 	{ CONN_LOGGED_IN, CONN_EV_CLOSE, c_do_logout },
254 	{ 0, 0, NULL }
255 };
256 
257 void
258 conn_fsm(struct connection *c, enum c_event event)
259 {
260 	int	i, ns;
261 
262 	for (i = 0; fsm[i].action != NULL; i++) {
263 		if (c->state & fsm[i].state && event == fsm[i].event) {
264 			ns = fsm[i].action(c, event);
265 			log_debug("conn_fsm: %s ev %s -> %s",
266 			   conn_state(c->state), conn_event(event),
267 			   conn_state(ns));
268 			if (ns == -1)
269 				/* XXX better please */
270 				fatalx("conn_fsm: action failed");
271 			c->state = ns;
272 			return;
273 		}
274 	}
275 	log_warnx("conn_fsm: unhandled state transition [%s, %s]",
276 		conn_state(c->state), conn_event(event));
277 	fatalx("bork bork bork");
278 }
279 
280 int
281 c_do_connect(struct connection *c, enum c_event ev)
282 {
283 	if (c->fd == -1)
284 		fatalx("c_do_connect, lost socket");
285 
286 	if (connect(c->fd, (struct sockaddr *)&c->config.TargetAddr,
287 	    c->config.TargetAddr.ss_len) == -1) {
288 		if (errno == EINPROGRESS) {
289 			event_add(&c->wev, NULL);
290 			return (CONN_XPT_WAIT);
291 		} else {
292 			log_warn("connect(%s)",
293 			    log_sockaddr(&c->config.TargetAddr));
294 			return (-1);
295 		}
296 	}
297 	/* move forward */
298 	return (c_do_login(c, CONN_EV_CONNECTED));
299 }
300 
301 int
302 c_do_login(struct connection *c, enum c_event ev)
303 {
304 	/* start a login session and hope for the best ... */
305 	initiator_login(c);
306 	return (CONN_IN_LOGIN);
307 }
308 
309 int
310 c_do_loggedin(struct connection *c, enum c_event ev)
311 {
312 	if (ev == CONN_EV_LOGGED_IN)
313 		vscsi_event(VSCSI_REQPROBE, c->session->target, 0);
314 	else
315 		initiator_discovery(c->session);
316 	return (CONN_LOGGED_IN);
317 }
318 
319 int
320 c_do_logout(struct connection *c, enum c_event ev)
321 {
322 	/* do full logout */
323 	return (CONN_FREE);
324 }
325 
326 const char *
327 conn_state(int s)
328 {
329 	static char buf[15];
330 
331 	switch (s) {
332 	case CONN_FREE:
333 		return "FREE";
334 	case CONN_XPT_WAIT:
335 		return "XPT_WAIT";
336 	case CONN_XPT_UP:
337 		return "XPT_UP";
338 	case CONN_IN_LOGIN:
339 		return "IN_LOGIN";
340 	case CONN_LOGGED_IN:
341 		return "LOGGED_IN";
342 	case CONN_IN_LOGOUT:
343 		return "IN_LOGOUT";
344 	case CONN_LOGOUT_REQ:
345 		return "LOGOUT_REQ";
346 	case CONN_CLEANUP_WAIT:
347 		return "CLEANUP_WAIT";
348 	case CONN_IN_CLEANUP:
349 		return "IN_CLEANUP";
350 	default:
351 		snprintf(buf, sizeof(buf), "UKNWN %x", s);
352 		return buf;
353 	}
354 	/* NOTREACHED */
355 }
356 
357 const char *
358 conn_event(enum c_event e)
359 {
360 	static char buf[15];
361 
362 	switch (e) {
363 	case CONN_EV_FAIL:
364 		return "fail";
365 	case CONN_EV_CONNECT:
366 		return "connect";
367 	case CONN_EV_CONNECTED:
368 		return "connected";
369 	case CONN_EV_LOGGED_IN:
370 		return "logged in";
371 	case CONN_EV_DISCOVERY:
372 		return "discovery";
373 	case CONN_EV_CLOSE:
374 		return "close";
375 	}
376 
377 	snprintf(buf, sizeof(buf), "UKNWN %d", e);
378 	return buf;
379 }
380