xref: /openbsd/usr.sbin/iscsid/connection.c (revision eda5eff4)
1 /*	$OpenBSD: connection.c,v 1.2 2010/09/24 10:44:39 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 log_debug("conn_task_issue");
208 	if (!TAILQ_EMPTY(&c->tasks))
209 		return 0;
210 
211 	TAILQ_INSERT_TAIL(&c->tasks, t, entry);
212 	conn_task_schedule(c);
213 	return 1;
214 }
215 
216 void
217 conn_task_schedule(struct connection *c)
218 {
219 	struct task *t = TAILQ_FIRST(&c->tasks);
220 	struct pdu *p, *np;
221 
222 	if (!t) {
223 		log_debug("conn_task_schedule: task is hiding");
224 		return;
225 	}
226 
227 	/* move pdus to the write queue */
228 	for (p = TAILQ_FIRST(&t->sendq); p != NULL; p = np) {
229 		np = TAILQ_NEXT(p, entry);
230 		TAILQ_REMOVE(&t->sendq, p, entry);
231 		conn_pdu_write(c, p);	/* maybe inline ? */
232 	}
233 }
234 
235 void
236 conn_pdu_write(struct connection *c, struct pdu *p)
237 {
238 	struct iscsi_pdu *ipdu;
239 
240 /* XXX I GUESS THIS SHOULD BE MOVED TO PDU SOMEHOW... */
241 	ipdu = pdu_getbuf(p, NULL, PDU_HEADER);
242 	switch (ISCSI_PDU_OPCODE(ipdu->opcode)) {
243 	case ISCSI_OP_TASK_REQUEST:
244 		ipdu->expstatsn = ntohl(c->expstatsn);
245 		break;
246 	}
247 
248 	TAILQ_INSERT_TAIL(&c->pdu_w, p, entry);
249 	event_add(&c->wev, NULL);
250 }
251 
252 /* connection state machine more or less as specified in the RFC */
253 struct {
254 	int		state;
255 	enum c_event	event;
256 	int		(*action)(struct connection *, enum c_event);
257 } fsm[] = {
258 	{ CONN_FREE, CONN_EV_CONNECT, c_do_connect },
259 	{ CONN_XPT_WAIT, CONN_EV_CONNECTED, c_do_login },
260 	{ CONN_IN_LOGIN, CONN_EV_LOGGED_IN, c_do_loggedin },
261 	{ CONN_IN_LOGIN, CONN_EV_DISCOVERY, c_do_loggedin },
262 	{ CONN_LOGGED_IN, CONN_EV_CLOSE, c_do_logout },
263 	{ 0, 0, NULL }
264 };
265 
266 void
267 conn_fsm(struct connection *c, enum c_event event)
268 {
269 	int	i, ns;
270 
271 	for (i = 0; fsm[i].action != NULL; i++) {
272 		if (c->state & fsm[i].state && event == fsm[i].event) {
273 			ns = fsm[i].action(c, event);
274 			log_debug("conn_fsm: %s ev %s -> %s",
275 			   conn_state(c->state), conn_event(event),
276 			   conn_state(ns));
277 			if (ns == -1)
278 				/* XXX better please */
279 				fatalx("conn_fsm: action failed");
280 			c->state = ns;
281 			return;
282 		}
283 	}
284 	log_warnx("conn_fsm: unhandled state transition [%s, %s]",
285 		conn_state(c->state), conn_event(event));
286 	fatalx("bork bork bork");
287 }
288 
289 int
290 c_do_connect(struct connection *c, enum c_event ev)
291 {
292 	if (c->fd == -1)
293 		fatalx("c_do_connect, lost socket");
294 
295 	if (connect(c->fd, (struct sockaddr *)&c->config.TargetAddr,
296 	    c->config.TargetAddr.ss_len) == -1) {
297 		if (errno == EINPROGRESS) {
298 			event_add(&c->wev, NULL);
299 			return (CONN_XPT_WAIT);
300 		} else {
301 			log_warn("connect(%s)",
302 			    log_sockaddr(&c->config.TargetAddr));
303 			return (-1);
304 		}
305 	}
306 	/* move forward */
307 	return (c_do_login(c, CONN_EV_CONNECTED));
308 }
309 
310 int
311 c_do_login(struct connection *c, enum c_event ev)
312 {
313 	/* start a login session and hope for the best ... */
314 	initiator_login(c);
315 	return (CONN_IN_LOGIN);
316 }
317 
318 int
319 c_do_loggedin(struct connection *c, enum c_event ev)
320 {
321 	if (ev == CONN_EV_LOGGED_IN)
322 		vscsi_event(VSCSI_REQPROBE, c->session->target, 0);
323 	else
324 		initiator_discovery(c->session);
325 	return (CONN_LOGGED_IN);
326 }
327 
328 int
329 c_do_logout(struct connection *c, enum c_event ev)
330 {
331 	/* do full logout */
332 	return (CONN_FREE);
333 }
334 
335 const char *
336 conn_state(int s)
337 {
338 	static char buf[15];
339 
340 	switch (s) {
341 	case CONN_FREE:
342 		return "FREE";
343 	case CONN_XPT_WAIT:
344 		return "XPT_WAIT";
345 	case CONN_XPT_UP:
346 		return "XPT_UP";
347 	case CONN_IN_LOGIN:
348 		return "IN_LOGIN";
349 	case CONN_LOGGED_IN:
350 		return "LOGGED_IN";
351 	case CONN_IN_LOGOUT:
352 		return "IN_LOGOUT";
353 	case CONN_LOGOUT_REQ:
354 		return "LOGOUT_REQ";
355 	case CONN_CLEANUP_WAIT:
356 		return "CLEANUP_WAIT";
357 	case CONN_IN_CLEANUP:
358 		return "IN_CLEANUP";
359 	default:
360 		snprintf(buf, sizeof(buf), "UKNWN %x", s);
361 		return buf;
362 	}
363 	/* NOTREACHED */
364 }
365 
366 const char *
367 conn_event(enum c_event e)
368 {
369 	static char buf[15];
370 
371 	switch (e) {
372 	case CONN_EV_FAIL:
373 		return "fail";
374 	case CONN_EV_CONNECT:
375 		return "connect";
376 	case CONN_EV_CONNECTED:
377 		return "connected";
378 	case CONN_EV_LOGGED_IN:
379 		return "logged in";
380 	case CONN_EV_DISCOVERY:
381 		return "discovery";
382 	case CONN_EV_CLOSE:
383 		return "close";
384 	}
385 
386 	snprintf(buf, sizeof(buf), "UKNWN %d", e);
387 	return buf;
388 }
389