summaryrefslogtreecommitdiffstats
path: root/usr.sbin/iscsid/initiator.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/iscsid/initiator.c')
-rw-r--r--usr.sbin/iscsid/initiator.c409
1 files changed, 409 insertions, 0 deletions
diff --git a/usr.sbin/iscsid/initiator.c b/usr.sbin/iscsid/initiator.c
new file mode 100644
index 00000000000..a94105ea25b
--- /dev/null
+++ b/usr.sbin/iscsid/initiator.c
@@ -0,0 +1,409 @@
+/* $OpenBSD: initiator.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */
+
+/*
+ * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <scsi/iscsi.h>
+
+#include <event.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "iscsid.h"
+#include "log.h"
+
+struct initiator *initiator;
+
+struct kvp *initiator_login_kvp(struct session *);
+char *default_initiator_name(void);
+
+struct initiator *
+initiator_init(void)
+{
+ if (!(initiator = calloc(1, sizeof(*initiator))))
+ fatal("initiator_init");
+
+ initiator->config.isid_base =
+ arc4random_uniform(0xffffff) | ISCSI_ISID_RAND;
+ initiator->config.isid_qual = arc4random_uniform(0xffff);
+ TAILQ_INIT(&initiator->sessions);
+ return (initiator);
+}
+
+void
+initiator_cleanup(struct initiator *i)
+{
+ struct session *s;
+
+ while ((s = TAILQ_FIRST(&i->sessions)) != NULL) {
+ TAILQ_REMOVE(&i->sessions, s, entry);
+ session_close(s);
+ }
+ free(initiator);
+}
+
+struct session *
+initiator_t2s(u_int target)
+{
+ struct session *s;
+
+ TAILQ_FOREACH(s, &initiator->sessions, entry) {
+ if (s->target == target)
+ return s;
+ }
+ return NULL;
+}
+
+struct session *
+session_find(struct initiator *i, char *name)
+{
+ struct session *s;
+
+ TAILQ_FOREACH(s, &initiator->sessions, entry) {
+ if (strcmp(s->config.SessionName, name) == 0)
+ return s;
+ }
+ return NULL;
+}
+
+struct session *
+session_new(struct initiator *i, u_int8_t st)
+{
+ struct session *s;
+
+ if (!(s = calloc(1, sizeof(*s))))
+ return NULL;
+
+ /* use the same qualifier unless there is a conflict */
+ s->isid_base = i->config.isid_base;
+ s->isid_qual = i->config.isid_qual;
+ s->cmdseqnum = arc4random();
+ s->itt = arc4random();
+ s->initiator = i;
+ s->state = SESS_FREE;
+
+ if (st == SESSION_TYPE_DISCOVERY)
+ s->target = 0;
+ else
+ s->target = s->initiator->target++;
+
+ TAILQ_INSERT_HEAD(&i->sessions, s, entry);
+ TAILQ_INIT(&s->connections);
+ TAILQ_INIT(&s->tasks);
+
+ return s;
+}
+
+void
+session_close(struct session *s)
+{
+ struct connection *c;
+
+ while ((c = TAILQ_FIRST(&s->connections)) != NULL)
+ conn_free(c);
+
+ free(s->config.TargetName);
+ free(s->config.InitiatorName);
+ free(s);
+}
+
+void
+session_config(struct session *s, struct session_config *sc)
+{
+ if (s->config.TargetName)
+ free(s->config.TargetName);
+ s->config.TargetName = NULL;
+ if (s->config.InitiatorName)
+ free(s->config.InitiatorName);
+ s->config.InitiatorName = NULL;
+
+ s->config = *sc;
+
+ if (sc->TargetName) {
+ s->config.TargetName = strdup(sc->TargetName);
+ if (s->config.TargetName == NULL)
+ fatal("strdup");
+ }
+ if (sc->InitiatorName) {
+ s->config.InitiatorName = strdup(sc->InitiatorName);
+ if (s->config.InitiatorName == NULL)
+ fatal("strdup");
+ } else
+ s->config.InitiatorName = default_initiator_name();
+}
+
+void
+session_task_issue(struct session *s, struct task *t)
+{
+ TAILQ_INSERT_TAIL(&s->tasks, t, entry);
+ session_schedule(s);
+}
+
+void
+session_schedule(struct session *s)
+{
+ struct task *t = TAILQ_FIRST(&s->tasks);
+ struct connection *c;
+
+ if (!t)
+ return;
+
+ /* XXX IMMEDIATE TASK NEED SPECIAL HANDLING !!!! */
+
+ /* wake up a idle connection or a not busy one */
+ /* XXX this needs more work as it makes the daemon go wrooOOOMM */
+ TAILQ_REMOVE(&s->tasks, t, entry);
+ TAILQ_FOREACH(c, &s->connections, entry)
+ if (conn_task_issue(c, t))
+ return;
+ /* all connections are busy readd task to the head */
+ TAILQ_INSERT_HEAD(&s->tasks, t, entry);
+}
+
+struct task_login {
+ struct task task;
+ struct connection *c;
+ u_int16_t tsih;
+ u_int8_t stage;
+};
+
+struct pdu *initiator_login_build(struct task_login *, struct kvp *);
+void initiator_login_cb(struct connection *, void *, struct pdu *);
+
+void initiator_discovery_cb(struct connection *, void *, struct pdu *);
+struct pdu *initiator_text_build(struct task *, struct session *, struct kvp *);
+
+struct kvp *
+initiator_login_kvp(struct session *s)
+{
+ struct kvp *kvp;
+
+ if (!(kvp = calloc(4, sizeof(*kvp))))
+ return NULL;
+ kvp[0].key = "AuthMethod";
+ kvp[0].value = "None";
+ kvp[1].key = "InitiatorName";
+ kvp[1].value = s->config.InitiatorName;
+
+ if (s->config.SessionType == SESSION_TYPE_DISCOVERY) {
+ kvp[2].key = "SessionType";
+ kvp[2].value = "Discovery";
+ } else {
+ kvp[2].key = "TargetName";
+ kvp[2].value = s->config.TargetName;
+ }
+
+ return kvp;
+}
+
+void
+initiator_login(struct connection *c)
+{
+ struct task_login *tl;
+ struct pdu *p;
+ struct kvp *kvp;
+
+ if (!(tl = calloc(1, sizeof(*tl)))) {
+ log_warn("initiator_login");
+ conn_fail(c);
+ return;
+ }
+ tl->c = c;
+ tl->stage = ISCSI_LOGIN_STG_SECNEG;
+
+ if (!(kvp = initiator_login_kvp(c->session))) {
+ log_warnx("initiator_login_kvp failed");
+ free(tl);
+ conn_fail(c);
+ return;
+ }
+
+ if (!(p = initiator_login_build(tl, kvp))) {
+ log_warnx("initiator_login_build failed");
+ free(tl);
+ conn_fail(c);
+ return;
+ }
+
+ free(kvp);
+
+ task_init(&tl->task, c->session, 1, tl, initiator_login_cb);
+ task_pdu_add(&tl->task, p);
+ /* XXX this is wrong, login needs to run on a specific connection */
+ session_task_issue(c->session, &tl->task);
+}
+
+struct pdu *
+initiator_login_build(struct task_login *tl, struct kvp *kvp)
+{
+ struct pdu *p;
+ struct iscsi_pdu_login_request *lreq;
+ int n;
+
+ if (!(p = pdu_new()))
+ return NULL;
+ if (!(lreq = pdu_gethdr(p)))
+ return NULL;
+
+ lreq->opcode = ISCSI_OP_LOGIN_REQUEST | ISCSI_OP_F_IMMEDIATE;
+ if (tl->stage == ISCSI_LOGIN_STG_SECNEG)
+ lreq->flags = ISCSI_LOGIN_F_T |
+ ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
+ ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
+ else if (tl->stage == ISCSI_LOGIN_STG_OPNEG)
+ lreq->flags = ISCSI_LOGIN_F_T |
+ ISCSI_LOGIN_F_CSG(ISCSI_LOGIN_STG_OPNEG) |
+ ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_FULL);
+
+ lreq->isid_base = htonl(tl->c->session->isid_base);
+ lreq->isid_qual = htons(tl->c->session->isid_qual);
+ lreq->tsih = tl->tsih;
+ lreq->cid = htons(tl->c->cid);
+ lreq->expstatsn = htonl(tl->c->expstatsn);
+
+ if ((n = text_to_pdu(kvp, p)) == -1)
+ return NULL;
+ n = htonl(n);
+ bcopy(&n, &lreq->ahslen, sizeof(n));
+
+ return p;
+}
+
+void
+initiator_login_cb(struct connection *c, void *arg, struct pdu *p)
+{
+ struct task_login *tl = arg;
+ struct iscsi_pdu_login_response *lresp;
+
+ lresp = pdu_getbuf(p, NULL, PDU_HEADER);
+ /* XXX handle packet would be great */
+ log_pdu(p, 1);
+ if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) {
+ log_debug("Unkown crap");
+ }
+
+ task_cleanup(&tl->task, c);
+ conn_loggedin(c);
+ free(tl);
+ pdu_free(p);
+}
+
+void
+initiator_discovery(struct session *s)
+{
+ struct task *t;
+ struct pdu *p;
+ struct kvp kvp[] = {
+ { "SendTargets", "All" },
+ { NULL, NULL }
+ };
+
+ if (!(t = calloc(1, sizeof(*t)))) {
+ log_warn("initiator_discovery");
+ return;
+ }
+
+ if (!(p = initiator_text_build(t, s, kvp))) {
+ log_warnx("initiator_text_build failed");
+ return;
+ }
+
+ task_init(t, s, 0, t, initiator_discovery_cb);
+ task_pdu_add(t, p);
+ session_task_issue(s, t);
+}
+
+struct pdu *
+initiator_text_build(struct task *t, struct session *s, struct kvp *kvp)
+{
+ struct pdu *p;
+ struct iscsi_pdu_text_request *lreq;
+ int n;
+
+ if (!(p = pdu_new()))
+ return NULL;
+ if (!(lreq = pdu_gethdr(p)))
+ return NULL;
+
+ lreq->opcode = ISCSI_OP_TEXT_REQUEST;
+ lreq->flags = ISCSI_TEXT_F_F;
+ lreq->ttt = 0xffffffff;
+
+ if ((n = text_to_pdu(kvp, p)) == -1)
+ return NULL;
+ n = htonl(n);
+ bcopy(&n, &lreq->ahslen, sizeof(n));
+
+ return p;
+}
+
+void
+initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p)
+{
+ struct iscsi_pdu_text_response *lresp;
+ u_char *buf = NULL;
+ struct kvp *kvp, *k;
+ size_t n, size;
+
+ lresp = pdu_getbuf(p, NULL, PDU_HEADER);
+ switch (ISCSI_PDU_OPCODE(lresp->opcode)) {
+ case ISCSI_OP_TEXT_RESPONSE:
+ buf = pdu_getbuf(p, &n, PDU_DATA);
+ if (buf == NULL)
+ goto fail;
+ size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
+ lresp->datalen[2];
+ if (size > n)
+ goto fail;
+ kvp = pdu_to_text(buf, size);
+ if (kvp == NULL)
+ goto fail;
+ log_debug("ISCSI_OP_TEXT_RESPONSE");
+ for (k = kvp; k->key; k++) {
+ log_debug("%s\t=>\t%s", k->key, k->value);
+ }
+ free(kvp);
+ free(arg);
+ conn_close(c);
+ break;
+ default:
+fail:
+ conn_fail(c);
+ }
+ pdu_free(p);
+}
+
+char *
+default_initiator_name(void)
+{
+ char *s, hostname[MAXHOSTNAMELEN];
+
+ if (gethostname(hostname, sizeof(hostname)))
+ strlcpy(hostname, "initiator", sizeof(hostname));
+ if ((s = strchr(hostname, '.')))
+ *s = '\0';
+ if (asprintf(&s, "%s:%s", ISCSID_BASE_NAME, hostname) == -1)
+ return ISCSID_BASE_NAME ":initiator";
+ return s;
+}