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