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