1 /* $OpenBSD: initiator.c,v 1.15 2015/01/16 15:57:06 deraadt 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/queue.h> 21 #include <sys/socket.h> 22 #include <sys/uio.h> 23 24 #include <scsi/iscsi.h> 25 26 #include <event.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <string.h> 30 #include <unistd.h> 31 #include <limits.h> 32 33 #include "iscsid.h" 34 #include "log.h" 35 36 struct initiator *initiator; 37 38 struct task_login { 39 struct task task; 40 struct connection *c; 41 u_int16_t tsih; 42 u_int8_t stage; 43 }; 44 45 struct task_logout { 46 struct task task; 47 struct connection *c; 48 u_int8_t reason; 49 }; 50 51 struct kvp *initiator_login_kvp(struct connection *, u_int8_t); 52 struct pdu *initiator_login_build(struct connection *, 53 struct task_login *); 54 struct pdu *initiator_text_build(struct task *, struct session *, 55 struct kvp *); 56 57 void initiator_login_cb(struct connection *, void *, struct pdu *); 58 void initiator_discovery_cb(struct connection *, void *, struct pdu *); 59 void initiator_logout_cb(struct connection *, void *, struct pdu *); 60 61 62 struct session_params initiator_sess_defaults; 63 struct connection_params initiator_conn_defaults; 64 65 struct initiator * 66 initiator_init(void) 67 { 68 if (!(initiator = calloc(1, sizeof(*initiator)))) 69 fatal("initiator_init"); 70 71 initiator->config.isid_base = 72 arc4random_uniform(0xffffff) | ISCSI_ISID_RAND; 73 initiator->config.isid_qual = arc4random_uniform(0xffff); 74 TAILQ_INIT(&initiator->sessions); 75 76 /* initialize initiator defaults */ 77 initiator_sess_defaults = iscsi_sess_defaults; 78 initiator_conn_defaults = iscsi_conn_defaults; 79 initiator_sess_defaults.MaxConnections = ISCSID_DEF_CONNS; 80 initiator_conn_defaults.MaxRecvDataSegmentLength = 65536; 81 82 return initiator; 83 } 84 85 void 86 initiator_cleanup(struct initiator *i) 87 { 88 struct session *s; 89 90 while ((s = TAILQ_FIRST(&i->sessions)) != NULL) { 91 TAILQ_REMOVE(&i->sessions, s, entry); 92 session_cleanup(s); 93 } 94 free(initiator); 95 } 96 97 void 98 initiator_shutdown(struct initiator *i) 99 { 100 struct session *s; 101 102 log_debug("initiator_shutdown: going down"); 103 104 TAILQ_FOREACH(s, &initiator->sessions, entry) 105 session_shutdown(s); 106 } 107 108 int 109 initiator_isdown(struct initiator *i) 110 { 111 struct session *s; 112 int inprogres = 0; 113 114 TAILQ_FOREACH(s, &initiator->sessions, entry) { 115 if ((s->state & SESS_RUNNING) && !(s->state & SESS_FREE)) 116 inprogres = 1; 117 } 118 return !inprogres; 119 } 120 121 struct session * 122 initiator_t2s(u_int target) 123 { 124 struct session *s; 125 126 TAILQ_FOREACH(s, &initiator->sessions, entry) { 127 if (s->target == target) 128 return s; 129 } 130 return NULL; 131 } 132 133 void 134 initiator_login(struct connection *c) 135 { 136 struct task_login *tl; 137 struct pdu *p; 138 139 if (!(tl = calloc(1, sizeof(*tl)))) { 140 log_warn("initiator_login"); 141 conn_fail(c); 142 return; 143 } 144 tl->c = c; 145 tl->stage = ISCSI_LOGIN_STG_SECNEG; 146 147 if (!(p = initiator_login_build(c, tl))) { 148 log_warn("initiator_login_build failed"); 149 free(tl); 150 conn_fail(c); 151 return; 152 } 153 154 task_init(&tl->task, c->session, 1, tl, initiator_login_cb, NULL); 155 task_pdu_add(&tl->task, p); 156 conn_task_issue(c, &tl->task); 157 } 158 159 void 160 initiator_discovery(struct session *s) 161 { 162 struct task *t; 163 struct pdu *p; 164 struct kvp kvp[] = { 165 { "SendTargets", "All" }, 166 { NULL, NULL } 167 }; 168 169 if (!(t = calloc(1, sizeof(*t)))) { 170 log_warn("initiator_discovery"); 171 /* XXX sess_fail(c); */ 172 return; 173 } 174 175 if (!(p = initiator_text_build(t, s, kvp))) { 176 log_warnx("initiator_text_build failed"); 177 free(t); 178 /* XXX sess_fail(c); */ 179 return; 180 } 181 182 task_init(t, s, 0, t, initiator_discovery_cb, NULL); 183 task_pdu_add(t, p); 184 session_task_issue(s, t); 185 } 186 187 void 188 initiator_logout(struct session *s, struct connection *c, u_int8_t reason) 189 { 190 struct task_logout *tl; 191 struct pdu *p; 192 struct iscsi_pdu_logout_request *loreq; 193 194 if (!(tl = calloc(1, sizeof(*tl)))) { 195 log_warn("initiator_logout"); 196 /* XXX sess_fail */ 197 return; 198 } 199 tl->c = c; 200 tl->reason = reason; 201 202 if (!(p = pdu_new())) { 203 log_warn("initiator_logout"); 204 /* XXX sess_fail */ 205 free(tl); 206 return; 207 } 208 if (!(loreq = pdu_gethdr(p))) { 209 log_warn("initiator_logout"); 210 /* XXX sess_fail */ 211 pdu_free(p); 212 free(tl); 213 return; 214 } 215 216 loreq->opcode = ISCSI_OP_LOGOUT_REQUEST; 217 loreq->flags = ISCSI_LOGOUT_F | reason; 218 if (reason != ISCSI_LOGOUT_CLOSE_SESS) 219 loreq->cid = c->cid; 220 221 task_init(&tl->task, s, 0, tl, initiator_logout_cb, NULL); 222 task_pdu_add(&tl->task, p); 223 if (c && (c->state & CONN_RUNNING)) 224 conn_task_issue(c, &tl->task); 225 else 226 session_logout_issue(s, &tl->task); 227 } 228 229 void 230 initiator_nop_in_imm(struct connection *c, struct pdu *p) 231 { 232 struct iscsi_pdu_nop_in *nopin; 233 struct task *t; 234 235 /* fixup NOP-IN to make it a NOP-OUT */ 236 nopin = pdu_getbuf(p, NULL, PDU_HEADER); 237 nopin->maxcmdsn = 0; 238 nopin->opcode = ISCSI_OP_I_NOP | ISCSI_OP_F_IMMEDIATE; 239 240 /* and schedule an immediate task */ 241 if (!(t = calloc(1, sizeof(*t)))) { 242 log_warn("initiator_nop_in_imm"); 243 pdu_free(p); 244 return; 245 } 246 247 task_init(t, c->session, 1, NULL, NULL, NULL); 248 t->itt = 0xffffffff; /* change ITT because it is just a ping reply */ 249 task_pdu_add(t, p); 250 conn_task_issue(c, t); 251 } 252 253 struct kvp * 254 initiator_login_kvp(struct connection *c, u_int8_t stage) 255 { 256 struct kvp *kvp; 257 size_t nkvp; 258 259 switch (stage) { 260 case ISCSI_LOGIN_STG_SECNEG: 261 if (!(kvp = calloc(4, sizeof(*kvp)))) 262 return NULL; 263 kvp[0].key = "AuthMethod"; 264 kvp[0].value = "None"; 265 kvp[1].key = "InitiatorName"; 266 kvp[1].value = c->session->config.InitiatorName; 267 268 if (c->session->config.SessionType == SESSION_TYPE_DISCOVERY) { 269 kvp[2].key = "SessionType"; 270 kvp[2].value = "Discovery"; 271 } else { 272 kvp[2].key = "TargetName"; 273 kvp[2].value = c->session->config.TargetName; 274 } 275 break; 276 case ISCSI_LOGIN_STG_OPNEG: 277 if (conn_gen_kvp(c, NULL, &nkvp) == -1) 278 return NULL; 279 nkvp += 1; /* add slot for terminator */ 280 if (!(kvp = calloc(nkvp, sizeof(*kvp)))) 281 return NULL; 282 if (conn_gen_kvp(c, kvp, &nkvp) == -1) { 283 free(kvp); 284 return NULL; 285 } 286 break; 287 default: 288 log_warnx("initiator_login_kvp: exit stage left"); 289 return NULL; 290 } 291 return kvp; 292 } 293 294 struct pdu * 295 initiator_login_build(struct connection *c, struct task_login *tl) 296 { 297 struct pdu *p; 298 struct kvp *kvp; 299 struct iscsi_pdu_login_request *lreq; 300 int n; 301 302 if (!(p = pdu_new())) 303 return NULL; 304 if (!(lreq = pdu_gethdr(p))) { 305 pdu_free(p); 306 return NULL; 307 } 308 309 lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE; 310 if (tl->stage == ISCSI_LOGIN_STG_SECNEG) 311 lreq->flags = ISCSI_LOGIN_F_T | 312 ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_SECNEG) | 313 ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_OPNEG); 314 else if (tl->stage == ISCSI_LOGIN_STG_OPNEG) 315 lreq->flags = ISCSI_LOGIN_F_T | 316 ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) | 317 ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL); 318 319 lreq->isid_base = htonl(tl->c->session->isid_base); 320 lreq->isid_qual = htons(tl->c->session->isid_qual); 321 lreq->tsih = tl->tsih; 322 lreq->cid = htons(tl->c->cid); 323 lreq->expstatsn = htonl(tl->c->expstatsn); 324 325 if (!(kvp = initiator_login_kvp(c, tl->stage))) { 326 log_warn("initiator_login_kvp failed"); 327 return NULL; 328 } 329 if ((n = text_to_pdu(kvp, p)) == -1) { 330 free(kvp); 331 return NULL; 332 } 333 free(kvp); 334 335 if (n > 8192) { 336 log_warn("initiator_login_build: help, I'm too verbose"); 337 pdu_free(p); 338 return NULL; 339 } 340 n = htonl(n); 341 /* copy 32bit value over ahslen and datalen */ 342 memcpy(&lreq->ahslen, &n, sizeof(n)); 343 344 return p; 345 } 346 347 struct pdu * 348 initiator_text_build(struct task *t, struct session *s, struct kvp *kvp) 349 { 350 struct pdu *p; 351 struct iscsi_pdu_text_request *lreq; 352 int n; 353 354 if (!(p = pdu_new())) 355 return NULL; 356 if (!(lreq = pdu_gethdr(p))) 357 return NULL; 358 359 lreq->opcode = ISCSI_OP_TEXT_REQUEST; 360 lreq->flags = ISCSI_TEXT_F_F; 361 lreq->ttt = 0xffffffff; 362 363 if ((n = text_to_pdu(kvp, p)) == -1) 364 return NULL; 365 n = htonl(n); 366 memcpy(&lreq->ahslen, &n, sizeof(n)); 367 368 return p; 369 } 370 371 void 372 initiator_login_cb(struct connection *c, void *arg, struct pdu *p) 373 { 374 struct task_login *tl = arg; 375 struct iscsi_pdu_login_response *lresp; 376 u_char *buf = NULL; 377 struct kvp *kvp; 378 size_t n, size; 379 380 lresp = pdu_getbuf(p, NULL, PDU_HEADER); 381 382 if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) { 383 log_warnx("Unexpected login response type %x", 384 ISCSI_PDU_OPCODE(lresp->opcode)); 385 conn_fail(c); 386 goto done; 387 } 388 389 if (lresp->flags & ISCSI_LOGIN_F_C) { 390 log_warnx("Incomplete login responses are unsupported"); 391 conn_fail(c); 392 goto done; 393 } 394 395 size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 | 396 lresp->datalen[2]; 397 buf = pdu_getbuf(p, &n, PDU_DATA); 398 if (size > n) { 399 log_warnx("Bad login response"); 400 conn_fail(c); 401 goto done; 402 } 403 404 if (buf) { 405 kvp = pdu_to_text(buf, size); 406 if (kvp == NULL) { 407 conn_fail(c); 408 goto done; 409 } 410 411 if (conn_parse_kvp(c, kvp) == -1) { 412 free(kvp); 413 conn_fail(c); 414 goto done; 415 } 416 free(kvp); 417 } 418 419 /* advance FSM if possible */ 420 if (lresp->flags & ISCSI_LOGIN_F_T) 421 tl->stage = ISCSI_LOGIN_F_NSG(lresp->flags); 422 423 switch (tl->stage) { 424 case ISCSI_LOGIN_STG_SECNEG: 425 case ISCSI_LOGIN_STG_OPNEG: 426 /* free no longer used pdu */ 427 pdu_free(p); 428 p = initiator_login_build(c, tl); 429 if (p == NULL) { 430 conn_fail(c); 431 goto done; 432 } 433 break; 434 case ISCSI_LOGIN_STG_FULL: 435 conn_fsm(c, CONN_EV_LOGGED_IN); 436 conn_task_cleanup(c, &tl->task); 437 free(tl); 438 goto done; 439 default: 440 log_warnx("initiator_login_cb: exit stage left"); 441 conn_fail(c); 442 goto done; 443 } 444 conn_task_cleanup(c, &tl->task); 445 /* add new pdu and re-issue the task */ 446 task_pdu_add(&tl->task, p); 447 conn_task_issue(c, &tl->task); 448 return; 449 done: 450 if (p) 451 pdu_free(p); 452 } 453 454 void 455 initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p) 456 { 457 struct task *t = arg; 458 struct iscsi_pdu_text_response *lresp; 459 u_char *buf = NULL; 460 struct kvp *kvp, *k; 461 size_t n, size; 462 463 lresp = pdu_getbuf(p, NULL, PDU_HEADER); 464 switch (ISCSI_PDU_OPCODE(lresp->opcode)) { 465 case ISCSI_OP_TEXT_RESPONSE: 466 size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 | 467 lresp->datalen[2]; 468 if (size == 0) { 469 /* empty response */ 470 session_shutdown(c->session); 471 break; 472 } 473 buf = pdu_getbuf(p, &n, PDU_DATA); 474 if (size > n || buf == NULL) 475 goto fail; 476 kvp = pdu_to_text(buf, size); 477 if (kvp == NULL) 478 goto fail; 479 log_debug("ISCSI_OP_TEXT_RESPONSE"); 480 for (k = kvp; k->key; k++) { 481 log_debug("%s\t=>\t%s", k->key, k->value); 482 } 483 free(kvp); 484 session_shutdown(c->session); 485 break; 486 default: 487 log_debug("initiator_discovery_cb: unexpected message type %x", 488 ISCSI_PDU_OPCODE(lresp->opcode)); 489 fail: 490 conn_fail(c); 491 pdu_free(p); 492 return; 493 } 494 conn_task_cleanup(c, t); 495 free(t); 496 pdu_free(p); 497 } 498 499 void 500 initiator_logout_cb(struct connection *c, void *arg, struct pdu *p) 501 { 502 struct task_logout *tl = arg; 503 struct iscsi_pdu_logout_response *loresp; 504 505 loresp = pdu_getbuf(p, NULL, PDU_HEADER); 506 log_debug("initiator_logout_cb: " 507 "response %d, Time2Wait %d, Time2Retain %d", 508 loresp->response, ntohs(loresp->time2wait), 509 ntohs(loresp->time2retain)); 510 511 switch (loresp->response) { 512 case ISCSI_LOGOUT_RESP_SUCCESS: 513 if (tl->reason == ISCSI_LOGOUT_CLOSE_SESS) { 514 conn_fsm(c, CONN_EV_LOGGED_OUT); 515 session_fsm(c->session, SESS_EV_CLOSED, NULL, 0); 516 } else { 517 conn_fsm(tl->c, CONN_EV_LOGGED_OUT); 518 session_fsm(c->session, SESS_EV_CONN_CLOSED, tl->c, 0); 519 } 520 break; 521 case ISCSI_LOGOUT_RESP_UNKN_CID: 522 /* connection ID not found, retry will not help */ 523 log_warnx("%s: logout failed, cid %d unknown, giving up\n", 524 tl->c->session->config.SessionName, 525 tl->c->cid); 526 conn_fsm(tl->c, CONN_EV_FREE); 527 break; 528 case ISCSI_LOGOUT_RESP_NO_SUPPORT: 529 case ISCSI_LOGOUT_RESP_ERROR: 530 default: 531 /* need to retry logout after loresp->time2wait secs */ 532 conn_fail(tl->c); 533 pdu_free(p); 534 return; 535 } 536 537 conn_task_cleanup(c, &tl->task); 538 free(tl); 539 pdu_free(p); 540 } 541 542 char * 543 default_initiator_name(void) 544 { 545 char *s, hostname[HOST_NAME_MAX+1]; 546 547 if (gethostname(hostname, sizeof(hostname))) 548 strlcpy(hostname, "initiator", sizeof(hostname)); 549 if ((s = strchr(hostname, '.'))) 550 *s = '\0'; 551 if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1) 552 return ISCSID_BASE_NAME ":initiator"; 553 return s; 554 } 555