initiator.c revision 1.9
1/* $OpenBSD: initiator.c,v 1.9 2011/05/04 21:00:04 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/param.h> 20#include <sys/types.h> 21#include <sys/queue.h> 22#include <sys/socket.h> 23#include <sys/uio.h> 24 25#include <scsi/iscsi.h> 26 27#include <event.h> 28#include <stdio.h> 29#include <stdlib.h> 30#include <string.h> 31#include <unistd.h> 32 33#include "iscsid.h" 34#include "log.h" 35 36struct initiator *initiator; 37 38struct task_login { 39 struct task task; 40 struct connection *c; 41 u_int16_t tsih; 42 u_int8_t stage; 43}; 44 45struct task_logout { 46 struct task task; 47 struct connection *c; 48 u_int8_t reason; 49}; 50 51struct kvp *initiator_login_kvp(struct connection *, u_int8_t); 52struct pdu *initiator_login_build(struct connection *, 53 struct task_login *); 54struct pdu *initiator_text_build(struct task *, struct session *, 55 struct kvp *); 56 57void initiator_login_cb(struct connection *, void *, struct pdu *); 58void initiator_discovery_cb(struct connection *, void *, struct pdu *); 59void initiator_logout_cb(struct connection *, void *, struct pdu *); 60 61 62struct session_params initiator_sess_defaults; 63struct connection_params initiator_conn_defaults; 64 65struct initiator * 66initiator_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 = 8; 80 initiator_conn_defaults.MaxRecvDataSegmentLength = 65536; 81 82 return initiator; 83} 84 85void 86initiator_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 97void 98initiator_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 108int 109initiator_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 121struct session * 122initiator_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 133void 134initiator_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 159void 160initiator_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 187void 188initiator_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 229void 230initiator_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 253struct kvp * 254initiator_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 if (!(kvp = calloc(nkvp, sizeof(*kvp)))) 280 return NULL; 281 if (conn_gen_kvp(c, kvp, &nkvp) == -1) { 282 free(kvp); 283 return NULL; 284 } 285 break; 286 default: 287 log_warnx("initiator_login_kvp: exit stage left"); 288 return NULL; 289 } 290 return kvp; 291} 292 293struct pdu * 294initiator_login_build(struct connection *c, struct task_login *tl) 295{ 296 struct pdu *p; 297 struct kvp *kvp; 298 struct iscsi_pdu_login_request *lreq; 299 int n; 300 301 if (!(p = pdu_new())) 302 return NULL; 303 if (!(lreq = pdu_gethdr(p))) { 304 pdu_free(p); 305 return NULL; 306 } 307 308 lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE; 309 if (tl->stage == ISCSI_LOGIN_STG_SECNEG) 310 lreq->flags = ISCSI_LOGIN_F_T | 311 ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_SECNEG) | 312 ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_OPNEG); 313 else if (tl->stage == ISCSI_LOGIN_STG_OPNEG) 314 lreq->flags = ISCSI_LOGIN_F_T | 315 ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) | 316 ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL); 317 318 lreq->isid_base = htonl(tl->c->session->isid_base); 319 lreq->isid_qual = htons(tl->c->session->isid_qual); 320 lreq->tsih = tl->tsih; 321 lreq->cid = htons(tl->c->cid); 322 lreq->expstatsn = htonl(tl->c->expstatsn); 323 324 if (!(kvp = initiator_login_kvp(c, tl->stage))) { 325 log_warn("initiator_login_kvp failed"); 326 return NULL; 327 } 328 if ((n = text_to_pdu(kvp, p)) == -1) { 329 free(kvp); 330 return NULL; 331 } 332 free(kvp); 333 334 if (n > 8192) { 335 log_warn("initiator_login_build: help, I'm too verbose"); 336 pdu_free(p); 337 return NULL; 338 } 339 n = htonl(n); 340 /* copy 32bit value over ahslen and datalen */ 341 bcopy(&n, &lreq->ahslen, sizeof(n)); 342 343 return p; 344} 345 346struct pdu * 347initiator_text_build(struct task *t, struct session *s, struct kvp *kvp) 348{ 349 struct pdu *p; 350 struct iscsi_pdu_text_request *lreq; 351 int n; 352 353 if (!(p = pdu_new())) 354 return NULL; 355 if (!(lreq = pdu_gethdr(p))) 356 return NULL; 357 358 lreq->opcode = ISCSI_OP_TEXT_REQUEST; 359 lreq->flags = ISCSI_TEXT_F_F; 360 lreq->ttt = 0xffffffff; 361 362 if ((n = text_to_pdu(kvp, p)) == -1) 363 return NULL; 364 n = htonl(n); 365 bcopy(&n, &lreq->ahslen, sizeof(n)); 366 367 return p; 368} 369 370void 371initiator_login_cb(struct connection *c, void *arg, struct pdu *p) 372{ 373 struct task_login *tl = arg; 374 struct iscsi_pdu_login_response *lresp; 375 u_char *buf = NULL; 376 struct kvp *kvp; 377 size_t n, size; 378 379 lresp = pdu_getbuf(p, NULL, PDU_HEADER); 380 381 if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) { 382 log_warnx("Unexpected login response type %x", 383 ISCSI_PDU_OPCODE(lresp->opcode)); 384 conn_fail(c); 385 goto done; 386 } 387 388 if (lresp->flags & ISCSI_LOGIN_F_C) { 389 log_warnx("Incomplete login responses are unsupported"); 390 conn_fail(c); 391 goto done; 392 } 393 394 size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 | 395 lresp->datalen[2]; 396 buf = pdu_getbuf(p, &n, PDU_DATA); 397 if (size > n) { 398 log_warnx("Bad login response"); 399 conn_fail(c); 400 goto done; 401 } 402 403 if (buf) { 404 kvp = pdu_to_text(buf, size); 405 if (kvp == NULL) { 406 conn_fail(c); 407 goto done; 408 } 409 410 if (conn_parse_kvp(c, kvp) == -1) { 411 free(kvp); 412 conn_fail(c); 413 goto done; 414 } 415 free(kvp); 416 } 417 418 /* advance FSM if possible */ 419 if (lresp->flags & ISCSI_LOGIN_F_T) 420 tl->stage = ISCSI_LOGIN_F_NSG(lresp->flags); 421 422 switch (tl->stage) { 423 case ISCSI_LOGIN_STG_SECNEG: 424 case ISCSI_LOGIN_STG_OPNEG: 425 /* free no longer used pdu */ 426 pdu_free(p); 427 p = initiator_login_build(c, tl); 428 if (p == NULL) { 429 conn_fail(c); 430 goto done; 431 } 432 break; 433 case ISCSI_LOGIN_STG_FULL: 434 conn_fsm(c, CONN_EV_LOGGED_IN); 435 goto done; 436 default: 437 log_warnx("initiator_login_cb: exit stage left"); 438 conn_fail(c); 439 goto done; 440 } 441 conn_task_cleanup(c, &tl->task); 442 /* add new pdu and re-issue the task */ 443 task_pdu_add(&tl->task, p); 444 conn_task_issue(c, &tl->task); 445 return; 446done: 447 conn_task_cleanup(c, &tl->task); 448 free(tl); 449 if (p) 450 pdu_free(p); 451} 452 453void 454initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p) 455{ 456 struct task *t = arg; 457 struct iscsi_pdu_text_response *lresp; 458 u_char *buf = NULL; 459 struct kvp *kvp, *k; 460 size_t n, size; 461 462 lresp = pdu_getbuf(p, NULL, PDU_HEADER); 463 switch (ISCSI_PDU_OPCODE(lresp->opcode)) { 464 case ISCSI_OP_TEXT_RESPONSE: 465 size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 | 466 lresp->datalen[2]; 467 if (size == 0) { 468 /* empty response */ 469 session_shutdown(c->session); 470 break; 471 } 472 buf = pdu_getbuf(p, &n, PDU_DATA); 473 if (size > n || buf == NULL) 474 goto fail; 475 kvp = pdu_to_text(buf, size); 476 if (kvp == NULL) 477 goto fail; 478 log_debug("ISCSI_OP_TEXT_RESPONSE"); 479 for (k = kvp; k->key; k++) { 480 log_debug("%s\t=>\t%s", k->key, k->value); 481 } 482 free(kvp); 483 session_shutdown(c->session); 484 break; 485 default: 486 log_debug("initiator_discovery_cb: unexpected message type %x", 487 ISCSI_PDU_OPCODE(lresp->opcode)); 488fail: 489 conn_fail(c); 490 } 491 conn_task_cleanup(c, t); 492 free(t); 493 pdu_free(p); 494} 495 496void 497initiator_logout_cb(struct connection *c, void *arg, struct pdu *p) 498{ 499 struct task_logout *tl = arg; 500 struct iscsi_pdu_logout_response *loresp; 501 502 loresp = pdu_getbuf(p, NULL, PDU_HEADER); 503 log_debug("initiator_logout_cb: " 504 "response %d, Time2Wait %d, Time2Retain %d", 505 loresp->response, loresp->time2wait, loresp->time2retain); 506 507 switch (loresp->response) { 508 case ISCSI_LOGOUT_RESP_SUCCESS: 509 if (tl->reason == ISCSI_LOGOUT_CLOSE_SESS) { 510 conn_fsm(c, CONN_EV_LOGGED_OUT); 511 session_fsm(c->session, SESS_EV_CLOSED, NULL); 512 } else { 513 conn_fsm(tl->c, CONN_EV_LOGGED_OUT); 514 session_fsm(c->session, SESS_EV_CONN_CLOSED, tl->c); 515 } 516 break; 517 case ISCSI_LOGOUT_RESP_UNKN_CID: 518 /* connection ID not found, retry will not help */ 519 log_warnx("%s: logout failed, cid %d unknown, giving up\n", 520 tl->c->session->config.SessionName, 521 tl->c->cid); 522 conn_fsm(tl->c, CONN_EV_FREE); 523 break; 524 case ISCSI_LOGOUT_RESP_NO_SUPPORT: 525 case ISCSI_LOGOUT_RESP_ERROR: 526 default: 527 /* need to retry logout after loresp->time2wait secs */ 528 conn_fail(tl->c); 529 break; 530 } 531 532 conn_task_cleanup(c, &tl->task); 533 free(tl); 534 pdu_free(p); 535} 536 537char * 538default_initiator_name(void) 539{ 540 char *s, hostname[MAXHOSTNAMELEN]; 541 542 if (gethostname(hostname, sizeof(hostname))) 543 strlcpy(hostname, "initiator", sizeof(hostname)); 544 if ((s = strchr(hostname, '.'))) 545 *s = '\0'; 546 if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1) 547 return ISCSID_BASE_NAME ":initiator"; 548 return s; 549} 550