initiator.c revision 1.7
1/* $OpenBSD: initiator.c,v 1.7 2011/04/27 19:02:07 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 kvp *initiator_login_kvp(struct session *); 39 40struct initiator * 41initiator_init(void) 42{ 43 if (!(initiator = calloc(1, sizeof(*initiator)))) 44 fatal("initiator_init"); 45 46 initiator->config.isid_base = 47 arc4random_uniform(0xffffff) | ISCSI_ISID_RAND; 48 initiator->config.isid_qual = arc4random_uniform(0xffff); 49 TAILQ_INIT(&initiator->sessions); 50 return (initiator); 51} 52 53void 54initiator_cleanup(struct initiator *i) 55{ 56 struct session *s; 57 58 while ((s = TAILQ_FIRST(&i->sessions)) != NULL) { 59 TAILQ_REMOVE(&i->sessions, s, entry); 60 session_cleanup(s); 61 } 62 free(initiator); 63} 64 65struct session * 66initiator_t2s(u_int target) 67{ 68 struct session *s; 69 70 TAILQ_FOREACH(s, &initiator->sessions, entry) { 71 if (s->target == target) 72 return s; 73 } 74 return NULL; 75} 76 77struct task_login { 78 struct task task; 79 struct connection *c; 80 u_int16_t tsih; 81 u_int8_t stage; 82}; 83 84struct task_logout { 85 struct task task; 86 struct connection *c; 87 u_int8_t reason; 88}; 89 90struct pdu *initiator_login_build(struct task_login *, struct kvp *); 91void initiator_login_cb(struct connection *, void *, struct pdu *); 92 93void initiator_discovery_cb(struct connection *, void *, struct pdu *); 94struct pdu *initiator_text_build(struct task *, struct session *, struct kvp *); 95 96void initiator_logout_cb(struct connection *, void *, struct pdu *); 97 98struct kvp * 99initiator_login_kvp(struct session *s) 100{ 101 struct kvp *kvp; 102 103 if (!(kvp = calloc(4, sizeof(*kvp)))) 104 return NULL; 105 kvp[0].key = "AuthMethod"; 106 kvp[0].value = "None"; 107 kvp[1].key = "InitiatorName"; 108 kvp[1].value = s->config.InitiatorName; 109 110 if (s->config.SessionType == SESSION_TYPE_DISCOVERY) { 111 kvp[2].key = "SessionType"; 112 kvp[2].value = "Discovery"; 113 } else { 114 kvp[2].key = "TargetName"; 115 kvp[2].value = s->config.TargetName; 116 } 117 118 return kvp; 119} 120 121void 122initiator_login(struct connection *c) 123{ 124 struct task_login *tl; 125 struct pdu *p; 126 struct kvp *kvp; 127 128 if (!(tl = calloc(1, sizeof(*tl)))) { 129 log_warn("initiator_login"); 130 conn_fail(c); 131 return; 132 } 133 tl->c = c; 134 tl->stage = ISCSI_LOGIN_STG_SECNEG; 135 136 if (!(kvp = initiator_login_kvp(c->session))) { 137 log_warn("initiator_login_kvp failed"); 138 free(tl); 139 conn_fail(c); 140 return; 141 } 142 143 if (!(p = initiator_login_build(tl, kvp))) { 144 log_warn("initiator_login_build failed"); 145 free(tl); 146 free(kvp); 147 conn_fail(c); 148 return; 149 } 150 151 free(kvp); 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 158struct pdu * 159initiator_login_build(struct task_login *tl, struct kvp *kvp) 160{ 161 struct pdu *p; 162 struct iscsi_pdu_login_request *lreq; 163 int n; 164 165 if (!(p = pdu_new())) 166 return NULL; 167 if (!(lreq = pdu_gethdr(p))) 168 return NULL; 169 170 lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE; 171 if (tl->stage == ISCSI_LOGIN_STG_SECNEG) 172 lreq->flags = ISCSI_LOGIN_F_T | 173 ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) | 174 ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL); 175 else if (tl->stage == ISCSI_LOGIN_STG_OPNEG) 176 lreq->flags = ISCSI_LOGIN_F_T | 177 ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) | 178 ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL); 179 180 lreq->isid_base = htonl(tl->c->session->isid_base); 181 lreq->isid_qual = htons(tl->c->session->isid_qual); 182 lreq->tsih = tl->tsih; 183 lreq->cid = htons(tl->c->cid); 184 lreq->expstatsn = htonl(tl->c->expstatsn); 185 186 if ((n = text_to_pdu(kvp, p)) == -1) 187 return NULL; 188 n = htonl(n); 189 bcopy(&n, &lreq->ahslen, sizeof(n)); 190 191 return p; 192} 193 194void 195initiator_login_cb(struct connection *c, void *arg, struct pdu *p) 196{ 197 struct task_login *tl = arg; 198 struct iscsi_pdu_login_response *lresp; 199 200 lresp = pdu_getbuf(p, NULL, PDU_HEADER); 201 /* XXX handle packet would be great */ 202 log_pdu(p, 1); 203 if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) { 204 log_debug("Unknown crap"); 205 } 206 207 conn_task_cleanup(c, &tl->task); 208 conn_loggedin(c); 209 free(tl); 210 pdu_free(p); 211} 212 213void 214initiator_discovery(struct session *s) 215{ 216 struct task *t; 217 struct pdu *p; 218 struct kvp kvp[] = { 219 { "SendTargets", "All" }, 220 { NULL, NULL } 221 }; 222 223 if (!(t = calloc(1, sizeof(*t)))) { 224 log_warn("initiator_discovery"); 225 /* XXX conn_fail(c); */ 226 return; 227 } 228 229 if (!(p = initiator_text_build(t, s, kvp))) { 230 log_warnx("initiator_text_build failed"); 231 free(t); 232 /* conn_fail(c); */ 233 return; 234 } 235 236 task_init(t, s, 0, t, initiator_discovery_cb, NULL); 237 task_pdu_add(t, p); 238 session_task_issue(s, t); 239} 240 241void 242initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p) 243{ 244 struct task *t = arg; 245 struct iscsi_pdu_text_response *lresp; 246 u_char *buf = NULL; 247 struct kvp *kvp, *k; 248 size_t n, size; 249 250 lresp = pdu_getbuf(p, NULL, PDU_HEADER); 251 switch (ISCSI_PDU_OPCODE(lresp->opcode)) { 252 case ISCSI_OP_TEXT_RESPONSE: 253 size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 | 254 lresp->datalen[2]; 255 if (size == 0) { 256 /* empty response */ 257 conn_logout(c); 258 break; 259 } 260 buf = pdu_getbuf(p, &n, PDU_DATA); 261 if (size > n || buf == NULL) 262 goto fail; 263 kvp = pdu_to_text(buf, size); 264 if (kvp == NULL) 265 goto fail; 266 log_debug("ISCSI_OP_TEXT_RESPONSE"); 267 for (k = kvp; k->key; k++) { 268 log_debug("%s\t=>\t%s", k->key, k->value); 269 } 270 free(kvp); 271 conn_logout(c); 272 break; 273 default: 274 log_debug("initiator_discovery_cb: unexpected message type %x", 275 ISCSI_PDU_OPCODE(lresp->opcode)); 276fail: 277 conn_fail(c); 278 } 279 conn_task_cleanup(c, t); 280 free(t); 281 pdu_free(p); 282} 283 284void 285initiator_logout(struct connection *c, u_int8_t reason, int onconn) 286{ 287 struct task_logout *tl; 288 struct pdu *p; 289 struct iscsi_pdu_logout_request *loreq; 290 291 if (!(tl = calloc(1, sizeof(*tl)))) { 292 log_warn("initiator_logout"); 293 conn_fail(c); 294 return; 295 } 296 tl->c = c; 297 tl->reason = reason; 298 299 if (!(p = pdu_new())) { 300 log_warn("initiator_logout"); 301 conn_fail(c); 302 return; 303 } 304 if (!(loreq = pdu_gethdr(p))) { 305 log_warn("initiator_logout"); 306 conn_fail(c); 307 return; 308 } 309 310 loreq->opcode = ISCSI_OP_LOGOUT_REQUEST; 311 loreq->flags = ISCSI_LOGOUT_F | reason; 312 if (reason != 0) 313 loreq->cid = c->cid; 314 315 task_init(&tl->task, c->session, 0, tl, initiator_logout_cb, NULL); 316 task_pdu_add(&tl->task, p); 317 if (onconn) 318 conn_task_issue(c, &tl->task); 319 else 320 session_task_issue(c->session, &tl->task); 321} 322 323void 324initiator_logout_cb(struct connection *c, void *arg, struct pdu *p) 325{ 326 struct task_logout *tl = arg; 327 struct iscsi_pdu_logout_response *loresp; 328 329 c = tl->c; 330 loresp = pdu_getbuf(p, NULL, PDU_HEADER); 331 log_debug("initiator_logout_cb: " 332 "reason %d, Time2Wait %d, Time2Retain %d", 333 loresp->response, loresp->time2wait, loresp->time2retain); 334 335 switch (loresp->response) { 336 case ISCSI_LOGOUT_RESP_SUCCESS: 337 conn_fsm(tl->c, CONN_EV_LOGGED_OUT); 338 break; 339 case ISCSI_LOGOUT_RESP_UNKN_CID: 340 /* connection ID not found, retry will not help */ 341 log_warnx("%s: logout failed, cid %d unknown, giving up\n", 342 tl->c->session->config.SessionName, 343 tl->c->cid); 344 break; 345 case ISCSI_LOGOUT_RESP_NO_SUPPORT: 346 case ISCSI_LOGOUT_RESP_ERROR: 347 default: 348 /* need to retry logout after loresp->time2wait secs */ 349 conn_fail(tl->c); 350 break; 351 } 352 353 conn_task_cleanup(c, &tl->task); 354 free(tl); 355 pdu_free(p); 356} 357 358void 359initiator_nop_in_imm(struct connection *c, struct pdu *p) 360{ 361 struct iscsi_pdu_nop_in *nopin; 362 struct task *t; 363 364 /* fixup NOP-IN to make it a NOP-OUT */ 365 nopin = pdu_getbuf(p, NULL, PDU_HEADER); 366 nopin->maxcmdsn = 0; 367 nopin->opcode = ISCSI_OP_I_NOP | ISCSI_OP_F_IMMEDIATE; 368 369 /* and schedule an immediate task */ 370 if (!(t = calloc(1, sizeof(*t)))) { 371 log_warn("initiator_nop_in_imm"); 372 pdu_free(p); 373 return; 374 } 375 376 task_init(t, c->session, 1, NULL, NULL, NULL); 377 t->itt = 0xffffffff; /* change ITT because it is just a ping reply */ 378 task_pdu_add(t, p); 379 conn_task_issue(c, t); 380} 381 382struct pdu * 383initiator_text_build(struct task *t, struct session *s, struct kvp *kvp) 384{ 385 struct pdu *p; 386 struct iscsi_pdu_text_request *lreq; 387 int n; 388 389 if (!(p = pdu_new())) 390 return NULL; 391 if (!(lreq = pdu_gethdr(p))) 392 return NULL; 393 394 lreq->opcode = ISCSI_OP_TEXT_REQUEST; 395 lreq->flags = ISCSI_TEXT_F_F; 396 lreq->ttt = 0xffffffff; 397 398 if ((n = text_to_pdu(kvp, p)) == -1) 399 return NULL; 400 n = htonl(n); 401 bcopy(&n, &lreq->ahslen, sizeof(n)); 402 403 return p; 404} 405 406char * 407default_initiator_name(void) 408{ 409 char *s, hostname[MAXHOSTNAMELEN]; 410 411 if (gethostname(hostname, sizeof(hostname))) 412 strlcpy(hostname, "initiator", sizeof(hostname)); 413 if ((s = strchr(hostname, '.'))) 414 *s = '\0'; 415 if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1) 416 return ISCSID_BASE_NAME ":initiator"; 417 return s; 418} 419