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