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