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