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