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