1 /* $OpenBSD: initiator.c,v 1.21 2025/01/28 20:41:44 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/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 static 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 int conn_is_leading(struct connection *); 52 struct kvp *initiator_login_kvp(struct connection *, u_int8_t); 53 struct pdu *initiator_login_build(struct connection *, 54 struct task_login *); 55 struct pdu *initiator_text_build(struct task *, struct session *, 56 struct kvp *); 57 58 void initiator_login_cb(struct connection *, void *, struct pdu *); 59 void initiator_discovery_cb(struct connection *, void *, struct pdu *); 60 void initiator_logout_cb(struct connection *, void *, struct pdu *); 61 62 struct session_params initiator_sess_defaults; 63 struct connection_params initiator_conn_defaults; 64 65 void 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 83 void 84 initiator_cleanup(void) 85 { 86 struct session *s; 87 88 while ((s = TAILQ_FIRST(&initiator->sessions)) != NULL) { 89 TAILQ_REMOVE(&initiator->sessions, s, entry); 90 session_cleanup(s); 91 } 92 free(initiator); 93 } 94 95 void 96 initiator_set_config(struct initiator_config *ic) 97 { 98 initiator->config = *ic; 99 } 100 101 struct initiator_config * 102 initiator_get_config(void) 103 { 104 return &initiator->config; 105 } 106 107 void 108 initiator_shutdown(void) 109 { 110 struct session *s; 111 112 log_debug("initiator_shutdown: going down"); 113 114 TAILQ_FOREACH(s, &initiator->sessions, entry) 115 session_shutdown(s); 116 } 117 118 int 119 initiator_isdown(void) 120 { 121 struct session *s; 122 int inprogres = 0; 123 124 TAILQ_FOREACH(s, &initiator->sessions, entry) { 125 if ((s->state & SESS_RUNNING) && !(s->state & SESS_FREE)) 126 inprogres = 1; 127 } 128 return !inprogres; 129 } 130 131 struct session * 132 initiator_new_session(u_int8_t st) 133 { 134 struct session *s; 135 136 if (!(s = calloc(1, sizeof(*s)))) 137 return NULL; 138 139 /* use the same qualifier unless there is a conflict */ 140 s->isid_base = initiator->config.isid_base; 141 s->isid_qual = initiator->config.isid_qual; 142 s->cmdseqnum = arc4random(); 143 s->itt = arc4random(); 144 s->state = SESS_INIT; 145 146 s->sev.sess = s; 147 evtimer_set(&s->sev.ev, session_fsm_callback, &s->sev); 148 149 if (st == SESSION_TYPE_DISCOVERY) 150 s->target = 0; 151 else 152 s->target = initiator->target++; 153 154 TAILQ_INIT(&s->connections); 155 TAILQ_INIT(&s->tasks); 156 157 TAILQ_INSERT_HEAD(&initiator->sessions, s, entry); 158 159 return s; 160 } 161 162 struct session * 163 initiator_find_session(char *name) 164 { 165 struct session *s; 166 167 TAILQ_FOREACH(s, &initiator->sessions, entry) { 168 if (strcmp(s->config.SessionName, name) == 0) 169 return s; 170 } 171 return NULL; 172 } 173 174 struct session * 175 initiator_t2s(u_int target) 176 { 177 struct session *s; 178 179 TAILQ_FOREACH(s, &initiator->sessions, entry) { 180 if (s->target == target) 181 return s; 182 } 183 return NULL; 184 } 185 186 struct session_head * 187 initiator_get_sessions(void) 188 { 189 return &initiator->sessions; 190 } 191 192 void 193 initiator_login(struct connection *c) 194 { 195 struct task_login *tl; 196 struct pdu *p; 197 198 if (!(tl = calloc(1, sizeof(*tl)))) { 199 log_warn("initiator_login"); 200 conn_fail(c); 201 return; 202 } 203 tl->c = c; 204 tl->stage = ISCSI_LOGIN_STG_SECNEG; 205 206 if (!(p = initiator_login_build(c, tl))) { 207 log_warn("initiator_login_build failed"); 208 free(tl); 209 conn_fail(c); 210 return; 211 } 212 213 task_init(&tl->task, c->session, 1, tl, initiator_login_cb, NULL); 214 task_pdu_add(&tl->task, p); 215 conn_task_issue(c, &tl->task); 216 } 217 218 void 219 initiator_discovery(struct session *s) 220 { 221 struct task *t; 222 struct pdu *p; 223 struct kvp kvp[] = { 224 { "SendTargets", "All" }, 225 { NULL, NULL } 226 }; 227 228 if (!(t = calloc(1, sizeof(*t)))) { 229 log_warn("initiator_discovery"); 230 /* XXX sess_fail(c); */ 231 return; 232 } 233 234 if (!(p = initiator_text_build(t, s, kvp))) { 235 log_warnx("initiator_text_build failed"); 236 free(t); 237 /* XXX sess_fail(c); */ 238 return; 239 } 240 241 task_init(t, s, 0, t, initiator_discovery_cb, NULL); 242 task_pdu_add(t, p); 243 session_task_issue(s, t); 244 } 245 246 void 247 initiator_logout(struct session *s, struct connection *c, u_int8_t reason) 248 { 249 struct task_logout *tl; 250 struct pdu *p; 251 struct iscsi_pdu_logout_request *loreq; 252 253 if (!(tl = calloc(1, sizeof(*tl)))) { 254 log_warn("initiator_logout"); 255 /* XXX sess_fail */ 256 return; 257 } 258 tl->c = c; 259 tl->reason = reason; 260 261 if (!(p = pdu_new())) { 262 log_warn("initiator_logout"); 263 /* XXX sess_fail */ 264 free(tl); 265 return; 266 } 267 if (!(loreq = pdu_gethdr(p))) { 268 log_warn("initiator_logout"); 269 /* XXX sess_fail */ 270 pdu_free(p); 271 free(tl); 272 return; 273 } 274 275 loreq->opcode = ISCSI_OP_LOGOUT_REQUEST; 276 loreq->flags = ISCSI_LOGOUT_F | reason; 277 if (reason != ISCSI_LOGOUT_CLOSE_SESS) 278 loreq->cid = c->cid; 279 280 task_init(&tl->task, s, 0, tl, initiator_logout_cb, NULL); 281 task_pdu_add(&tl->task, p); 282 if (c && (c->state & CONN_RUNNING)) 283 conn_task_issue(c, &tl->task); 284 else 285 session_logout_issue(s, &tl->task); 286 } 287 288 void 289 initiator_nop_in_imm(struct connection *c, struct pdu *p) 290 { 291 struct iscsi_pdu_nop_in *nopin; 292 struct task *t; 293 294 /* fixup NOP-IN to make it a NOP-OUT */ 295 nopin = pdu_getbuf(p, NULL, PDU_HEADER); 296 nopin->maxcmdsn = 0; 297 nopin->opcode = ISCSI_OP_I_NOP | ISCSI_OP_F_IMMEDIATE; 298 299 /* and schedule an immediate task */ 300 if (!(t = calloc(1, sizeof(*t)))) { 301 log_warn("initiator_nop_in_imm"); 302 pdu_free(p); 303 return; 304 } 305 306 task_init(t, c->session, 1, NULL, NULL, NULL); 307 t->itt = 0xffffffff; /* change ITT because it is just a ping reply */ 308 task_pdu_add(t, p); 309 conn_task_issue(c, t); 310 } 311 312 int 313 conn_is_leading(struct connection *c) 314 { 315 return c == TAILQ_FIRST(&c->session->connections); 316 } 317 318 #define MINE_NOT_DEFAULT(c, k) ((c)->mine.k != iscsi_conn_defaults.k) 319 320 struct kvp * 321 initiator_login_kvp(struct connection *c, u_int8_t stage) 322 { 323 struct kvp *kvp = NULL; 324 size_t i = 0, len; 325 const char *discovery[] = {"SessionType", "InitiatorName", 326 "AuthMethod", NULL}; 327 const char *leading_only[] = {"MaxConnections", "InitialR2T", 328 "ImmediateData", "MaxBurstLength", "FirstBurstLength", 329 "DefaultTime2Wait", "DefaultTime2Retain", "MaxOutstandingR2T", 330 "DataPDUInOrder", "DataSequenceInOrder", "ErrorRecoveryLevel", 331 NULL}; 332 const char *opneg_always[] = {"HeaderDigest", "DataDigest", NULL}; 333 const char *secneg[] = {"SessionType", "InitiatorName", "TargetName", 334 "AuthMethod", NULL}; 335 const char **p, **q; 336 337 switch (stage) { 338 case ISCSI_LOGIN_STG_SECNEG: 339 if (c->session->config.SessionType == SESSION_TYPE_DISCOVERY) { 340 len = sizeof(discovery) / sizeof(*discovery); 341 q = discovery; 342 } else { 343 len = sizeof(secneg) / sizeof(*secneg); 344 q = secneg; 345 } 346 if (!(kvp = calloc(len + 1, sizeof(*kvp)))) 347 return NULL; 348 for (p = q; *p != NULL; i++, p++) 349 if (kvp_set_from_mine(&kvp[i], *p, c)) 350 goto fail; 351 break; 352 case ISCSI_LOGIN_STG_OPNEG: 353 len = sizeof(opneg_always) / sizeof(*opneg_always); 354 if (conn_is_leading(c)) 355 len += sizeof(leading_only) / sizeof(*leading_only); 356 if (MINE_NOT_DEFAULT(c, MaxRecvDataSegmentLength)) 357 len++; 358 if (!(kvp = calloc(len + 1, sizeof(*kvp)))) 359 return NULL; 360 for (p = opneg_always; *p != NULL; i++, p++) 361 if (kvp_set_from_mine(&kvp[i], *p, c)) 362 goto fail; 363 if (conn_is_leading(c)) 364 for (p = leading_only; *p != NULL; i++, p++) 365 if (kvp_set_from_mine(&kvp[i], *p, c)) 366 goto fail; 367 if (MINE_NOT_DEFAULT(c, MaxRecvDataSegmentLength) && 368 kvp_set_from_mine(&kvp[i], "MaxRecvDataSegmentLength", c)) 369 goto fail; 370 break; 371 default: 372 log_warnx("initiator_login_kvp: exit stage left"); 373 return NULL; 374 } 375 return kvp; 376 fail: 377 kvp_free(kvp); 378 return NULL; 379 } 380 381 #undef MINE_NOT_DEFAULT 382 struct pdu * 383 initiator_login_build(struct connection *c, struct task_login *tl) 384 { 385 struct pdu *p; 386 struct kvp *kvp; 387 struct iscsi_pdu_login_request *lreq; 388 int n; 389 390 if (!(p = pdu_new())) 391 return NULL; 392 if (!(lreq = pdu_gethdr(p))) { 393 pdu_free(p); 394 return NULL; 395 } 396 397 lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE; 398 if (tl->stage == ISCSI_LOGIN_STG_SECNEG) 399 lreq->flags = ISCSI_LOGIN_F_T | 400 ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_SECNEG) | 401 ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_OPNEG); 402 else if (tl->stage == ISCSI_LOGIN_STG_OPNEG) 403 lreq->flags = ISCSI_LOGIN_F_T | 404 ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) | 405 ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL); 406 407 lreq->isid_base = htonl(tl->c->session->isid_base); 408 lreq->isid_qual = htons(tl->c->session->isid_qual); 409 lreq->tsih = tl->tsih; 410 lreq->cid = htons(tl->c->cid); 411 lreq->expstatsn = htonl(tl->c->expstatsn); 412 413 if (!(kvp = initiator_login_kvp(c, tl->stage))) { 414 log_warn("initiator_login_kvp failed"); 415 return NULL; 416 } 417 if ((n = text_to_pdu(kvp, p)) == -1) { 418 kvp_free(kvp); 419 return NULL; 420 } 421 kvp_free(kvp); 422 423 if (n > 8192) { 424 log_warn("initiator_login_build: help, I'm too verbose"); 425 pdu_free(p); 426 return NULL; 427 } 428 n = htonl(n); 429 /* copy 32bit value over ahslen and datalen */ 430 memcpy(&lreq->ahslen, &n, sizeof(n)); 431 432 return p; 433 } 434 435 struct pdu * 436 initiator_text_build(struct task *t, struct session *s, struct kvp *kvp) 437 { 438 struct pdu *p; 439 struct iscsi_pdu_text_request *lreq; 440 int n; 441 442 if (!(p = pdu_new())) 443 return NULL; 444 if (!(lreq = pdu_gethdr(p))) 445 return NULL; 446 447 lreq->opcode = ISCSI_OP_TEXT_REQUEST; 448 lreq->flags = ISCSI_TEXT_F_F; 449 lreq->ttt = 0xffffffff; 450 451 if ((n = text_to_pdu(kvp, p)) == -1) 452 return NULL; 453 n = htonl(n); 454 memcpy(&lreq->ahslen, &n, sizeof(n)); 455 456 return p; 457 } 458 459 void 460 initiator_login_cb(struct connection *c, void *arg, struct pdu *p) 461 { 462 struct task_login *tl = arg; 463 struct iscsi_pdu_login_response *lresp; 464 u_char *buf = NULL; 465 struct kvp *kvp; 466 size_t n, size; 467 468 lresp = pdu_getbuf(p, NULL, PDU_HEADER); 469 470 if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) { 471 log_warnx("Unexpected login response type %x", 472 ISCSI_PDU_OPCODE(lresp->opcode)); 473 conn_fail(c); 474 goto done; 475 } 476 477 if (lresp->flags & ISCSI_LOGIN_F_C) { 478 log_warnx("Incomplete login responses are unsupported"); 479 conn_fail(c); 480 goto done; 481 } 482 483 size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 | 484 lresp->datalen[2]; 485 buf = pdu_getbuf(p, &n, PDU_DATA); 486 if (size > n) { 487 log_warnx("Bad login response"); 488 conn_fail(c); 489 goto done; 490 } 491 492 if (buf) { 493 kvp = pdu_to_text(buf, size); 494 if (kvp == NULL) { 495 conn_fail(c); 496 goto done; 497 } 498 499 if (conn_parse_kvp(c, kvp) == -1) { 500 kvp_free(kvp); 501 conn_fail(c); 502 goto done; 503 } 504 kvp_free(kvp); 505 } 506 507 /* advance FSM if possible */ 508 if (lresp->flags & ISCSI_LOGIN_F_T) 509 tl->stage = ISCSI_LOGIN_F_NSG(lresp->flags); 510 511 switch (tl->stage) { 512 case ISCSI_LOGIN_STG_SECNEG: 513 case ISCSI_LOGIN_STG_OPNEG: 514 /* free no longer used pdu */ 515 pdu_free(p); 516 p = initiator_login_build(c, tl); 517 if (p == NULL) { 518 conn_fail(c); 519 goto done; 520 } 521 break; 522 case ISCSI_LOGIN_STG_FULL: 523 conn_fsm(c, CONN_EV_LOGGED_IN); 524 conn_task_cleanup(c, &tl->task); 525 free(tl); 526 goto done; 527 default: 528 log_warnx("initiator_login_cb: exit stage left"); 529 conn_fail(c); 530 goto done; 531 } 532 conn_task_cleanup(c, &tl->task); 533 /* add new pdu and re-issue the task */ 534 task_pdu_add(&tl->task, p); 535 conn_task_issue(c, &tl->task); 536 return; 537 done: 538 if (p) 539 pdu_free(p); 540 } 541 542 void 543 initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p) 544 { 545 struct task *t = arg; 546 struct iscsi_pdu_text_response *lresp; 547 u_char *buf = NULL; 548 struct kvp *kvp, *k; 549 size_t n, size; 550 551 lresp = pdu_getbuf(p, NULL, PDU_HEADER); 552 switch (ISCSI_PDU_OPCODE(lresp->opcode)) { 553 case ISCSI_OP_TEXT_RESPONSE: 554 size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 | 555 lresp->datalen[2]; 556 if (size == 0) { 557 /* empty response */ 558 session_shutdown(c->session); 559 break; 560 } 561 buf = pdu_getbuf(p, &n, PDU_DATA); 562 if (size > n || buf == NULL) 563 goto fail; 564 kvp = pdu_to_text(buf, size); 565 if (kvp == NULL) 566 goto fail; 567 log_debug("ISCSI_OP_TEXT_RESPONSE"); 568 for (k = kvp; k->key; k++) { 569 log_debug("%s\t=>\t%s", k->key, k->value); 570 } 571 kvp_free(kvp); 572 session_shutdown(c->session); 573 break; 574 default: 575 log_debug("initiator_discovery_cb: unexpected message type %x", 576 ISCSI_PDU_OPCODE(lresp->opcode)); 577 fail: 578 conn_fail(c); 579 pdu_free(p); 580 return; 581 } 582 conn_task_cleanup(c, t); 583 free(t); 584 pdu_free(p); 585 } 586 587 void 588 initiator_logout_cb(struct connection *c, void *arg, struct pdu *p) 589 { 590 struct task_logout *tl = arg; 591 struct iscsi_pdu_logout_response *loresp; 592 593 loresp = pdu_getbuf(p, NULL, PDU_HEADER); 594 log_debug("initiator_logout_cb: " 595 "response %d, Time2Wait %d, Time2Retain %d", 596 loresp->response, ntohs(loresp->time2wait), 597 ntohs(loresp->time2retain)); 598 599 switch (loresp->response) { 600 case ISCSI_LOGOUT_RESP_SUCCESS: 601 if (tl->reason == ISCSI_LOGOUT_CLOSE_SESS) { 602 conn_fsm(c, CONN_EV_LOGGED_OUT); 603 session_fsm(&c->session->sev, SESS_EV_CLOSED, 0); 604 } else { 605 conn_fsm(tl->c, CONN_EV_LOGGED_OUT); 606 session_fsm(&tl->c->sev, SESS_EV_CONN_CLOSED, 0); 607 } 608 break; 609 case ISCSI_LOGOUT_RESP_UNKN_CID: 610 /* connection ID not found, retry will not help */ 611 log_warnx("%s: logout failed, cid %d unknown, giving up\n", 612 tl->c->session->config.SessionName, 613 tl->c->cid); 614 conn_fsm(tl->c, CONN_EV_FREE); 615 break; 616 case ISCSI_LOGOUT_RESP_NO_SUPPORT: 617 case ISCSI_LOGOUT_RESP_ERROR: 618 default: 619 /* need to retry logout after loresp->time2wait secs */ 620 conn_fail(tl->c); 621 pdu_free(p); 622 return; 623 } 624 625 conn_task_cleanup(c, &tl->task); 626 free(tl); 627 pdu_free(p); 628 } 629 630 char * 631 default_initiator_name(void) 632 { 633 char *s, hostname[HOST_NAME_MAX+1]; 634 635 if (gethostname(hostname, sizeof(hostname))) 636 strlcpy(hostname, "initiator", sizeof(hostname)); 637 if ((s = strchr(hostname, '.'))) 638 *s = '\0'; 639 if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1) 640 return ISCSID_BASE_NAME ":initiator"; 641 return s; 642 } 643