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