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