File: [local] / src / usr.sbin / iscsid / initiator.c (download)
Revision 1.15, Fri Jan 16 15:57:06 2015 UTC (9 years, 4 months ago) by deraadt
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9, OPENBSD_6_8_BASE, OPENBSD_6_8, OPENBSD_6_7_BASE, OPENBSD_6_7, OPENBSD_6_6_BASE, OPENBSD_6_6, OPENBSD_6_5_BASE, OPENBSD_6_5, OPENBSD_6_4_BASE, OPENBSD_6_4, OPENBSD_6_3_BASE, OPENBSD_6_3, OPENBSD_6_2_BASE, OPENBSD_6_2, OPENBSD_6_1_BASE, OPENBSD_6_1, OPENBSD_6_0_BASE, OPENBSD_6_0, OPENBSD_5_9_BASE, OPENBSD_5_9, OPENBSD_5_8_BASE, OPENBSD_5_8, OPENBSD_5_7_BASE, OPENBSD_5_7, HEAD Changes since 1.14: +3 -3 lines
move to <limits.h> where possible, annotate <sys/param.h> otherwise
|
/* $OpenBSD: initiator.c,v 1.15 2015/01/16 15:57:06 deraadt 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/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 <limits.h>
#include "iscsid.h"
#include "log.h"
struct initiator *initiator;
struct task_login {
struct task task;
struct connection *c;
u_int16_t tsih;
u_int8_t stage;
};
struct task_logout {
struct task task;
struct connection *c;
u_int8_t reason;
};
struct kvp *initiator_login_kvp(struct connection *, u_int8_t);
struct pdu *initiator_login_build(struct connection *,
struct task_login *);
struct pdu *initiator_text_build(struct task *, struct session *,
struct kvp *);
void initiator_login_cb(struct connection *, void *, struct pdu *);
void initiator_discovery_cb(struct connection *, void *, struct pdu *);
void initiator_logout_cb(struct connection *, void *, struct pdu *);
struct session_params initiator_sess_defaults;
struct connection_params initiator_conn_defaults;
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);
/* initialize initiator defaults */
initiator_sess_defaults = iscsi_sess_defaults;
initiator_conn_defaults = iscsi_conn_defaults;
initiator_sess_defaults.MaxConnections = ISCSID_DEF_CONNS;
initiator_conn_defaults.MaxRecvDataSegmentLength = 65536;
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_cleanup(s);
}
free(initiator);
}
void
initiator_shutdown(struct initiator *i)
{
struct session *s;
log_debug("initiator_shutdown: going down");
TAILQ_FOREACH(s, &initiator->sessions, entry)
session_shutdown(s);
}
int
initiator_isdown(struct initiator *i)
{
struct session *s;
int inprogres = 0;
TAILQ_FOREACH(s, &initiator->sessions, entry) {
if ((s->state & SESS_RUNNING) && !(s->state & SESS_FREE))
inprogres = 1;
}
return !inprogres;
}
struct session *
initiator_t2s(u_int target)
{
struct session *s;
TAILQ_FOREACH(s, &initiator->sessions, entry) {
if (s->target == target)
return s;
}
return NULL;
}
void
initiator_login(struct connection *c)
{
struct task_login *tl;
struct pdu *p;
if (!(tl = calloc(1, sizeof(*tl)))) {
log_warn("initiator_login");
conn_fail(c);
return;
}
tl->c = c;
tl->stage = ISCSI_LOGIN_STG_SECNEG;
if (!(p = initiator_login_build(c, tl))) {
log_warn("initiator_login_build failed");
free(tl);
conn_fail(c);
return;
}
task_init(&tl->task, c->session, 1, tl, initiator_login_cb, NULL);
task_pdu_add(&tl->task, p);
conn_task_issue(c, &tl->task);
}
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");
/* XXX sess_fail(c); */
return;
}
if (!(p = initiator_text_build(t, s, kvp))) {
log_warnx("initiator_text_build failed");
free(t);
/* XXX sess_fail(c); */
return;
}
task_init(t, s, 0, t, initiator_discovery_cb, NULL);
task_pdu_add(t, p);
session_task_issue(s, t);
}
void
initiator_logout(struct session *s, struct connection *c, u_int8_t reason)
{
struct task_logout *tl;
struct pdu *p;
struct iscsi_pdu_logout_request *loreq;
if (!(tl = calloc(1, sizeof(*tl)))) {
log_warn("initiator_logout");
/* XXX sess_fail */
return;
}
tl->c = c;
tl->reason = reason;
if (!(p = pdu_new())) {
log_warn("initiator_logout");
/* XXX sess_fail */
free(tl);
return;
}
if (!(loreq = pdu_gethdr(p))) {
log_warn("initiator_logout");
/* XXX sess_fail */
pdu_free(p);
free(tl);
return;
}
loreq->opcode = ISCSI_OP_LOGOUT_REQUEST;
loreq->flags = ISCSI_LOGOUT_F | reason;
if (reason != ISCSI_LOGOUT_CLOSE_SESS)
loreq->cid = c->cid;
task_init(&tl->task, s, 0, tl, initiator_logout_cb, NULL);
task_pdu_add(&tl->task, p);
if (c && (c->state & CONN_RUNNING))
conn_task_issue(c, &tl->task);
else
session_logout_issue(s, &tl->task);
}
void
initiator_nop_in_imm(struct connection *c, struct pdu *p)
{
struct iscsi_pdu_nop_in *nopin;
struct task *t;
/* fixup NOP-IN to make it a NOP-OUT */
nopin = pdu_getbuf(p, NULL, PDU_HEADER);
nopin->maxcmdsn = 0;
nopin->opcode = ISCSI_OP_I_NOP | ISCSI_OP_F_IMMEDIATE;
/* and schedule an immediate task */
if (!(t = calloc(1, sizeof(*t)))) {
log_warn("initiator_nop_in_imm");
pdu_free(p);
return;
}
task_init(t, c->session, 1, NULL, NULL, NULL);
t->itt = 0xffffffff; /* change ITT because it is just a ping reply */
task_pdu_add(t, p);
conn_task_issue(c, t);
}
struct kvp *
initiator_login_kvp(struct connection *c, u_int8_t stage)
{
struct kvp *kvp;
size_t nkvp;
switch (stage) {
case ISCSI_LOGIN_STG_SECNEG:
if (!(kvp = calloc(4, sizeof(*kvp))))
return NULL;
kvp[0].key = "AuthMethod";
kvp[0].value = "None";
kvp[1].key = "InitiatorName";
kvp[1].value = c->session->config.InitiatorName;
if (c->session->config.SessionType == SESSION_TYPE_DISCOVERY) {
kvp[2].key = "SessionType";
kvp[2].value = "Discovery";
} else {
kvp[2].key = "TargetName";
kvp[2].value = c->session->config.TargetName;
}
break;
case ISCSI_LOGIN_STG_OPNEG:
if (conn_gen_kvp(c, NULL, &nkvp) == -1)
return NULL;
nkvp += 1; /* add slot for terminator */
if (!(kvp = calloc(nkvp, sizeof(*kvp))))
return NULL;
if (conn_gen_kvp(c, kvp, &nkvp) == -1) {
free(kvp);
return NULL;
}
break;
default:
log_warnx("initiator_login_kvp: exit stage left");
return NULL;
}
return kvp;
}
struct pdu *
initiator_login_build(struct connection *c, struct task_login *tl)
{
struct pdu *p;
struct kvp *kvp;
struct iscsi_pdu_login_request *lreq;
int n;
if (!(p = pdu_new()))
return NULL;
if (!(lreq = pdu_gethdr(p))) {
pdu_free(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_SECNEG) |
ISCSI_LOGIN_F_NSG(ISCSI_LOGIN_STG_OPNEG);
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 (!(kvp = initiator_login_kvp(c, tl->stage))) {
log_warn("initiator_login_kvp failed");
return NULL;
}
if ((n = text_to_pdu(kvp, p)) == -1) {
free(kvp);
return NULL;
}
free(kvp);
if (n > 8192) {
log_warn("initiator_login_build: help, I'm too verbose");
pdu_free(p);
return NULL;
}
n = htonl(n);
/* copy 32bit value over ahslen and datalen */
memcpy(&lreq->ahslen, &n, sizeof(n));
return p;
}
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);
memcpy(&lreq->ahslen, &n, 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;
u_char *buf = NULL;
struct kvp *kvp;
size_t n, size;
lresp = pdu_getbuf(p, NULL, PDU_HEADER);
if (ISCSI_PDU_OPCODE(lresp->opcode) != ISCSI_OP_LOGIN_RESPONSE) {
log_warnx("Unexpected login response type %x",
ISCSI_PDU_OPCODE(lresp->opcode));
conn_fail(c);
goto done;
}
if (lresp->flags & ISCSI_LOGIN_F_C) {
log_warnx("Incomplete login responses are unsupported");
conn_fail(c);
goto done;
}
size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
lresp->datalen[2];
buf = pdu_getbuf(p, &n, PDU_DATA);
if (size > n) {
log_warnx("Bad login response");
conn_fail(c);
goto done;
}
if (buf) {
kvp = pdu_to_text(buf, size);
if (kvp == NULL) {
conn_fail(c);
goto done;
}
if (conn_parse_kvp(c, kvp) == -1) {
free(kvp);
conn_fail(c);
goto done;
}
free(kvp);
}
/* advance FSM if possible */
if (lresp->flags & ISCSI_LOGIN_F_T)
tl->stage = ISCSI_LOGIN_F_NSG(lresp->flags);
switch (tl->stage) {
case ISCSI_LOGIN_STG_SECNEG:
case ISCSI_LOGIN_STG_OPNEG:
/* free no longer used pdu */
pdu_free(p);
p = initiator_login_build(c, tl);
if (p == NULL) {
conn_fail(c);
goto done;
}
break;
case ISCSI_LOGIN_STG_FULL:
conn_fsm(c, CONN_EV_LOGGED_IN);
conn_task_cleanup(c, &tl->task);
free(tl);
goto done;
default:
log_warnx("initiator_login_cb: exit stage left");
conn_fail(c);
goto done;
}
conn_task_cleanup(c, &tl->task);
/* add new pdu and re-issue the task */
task_pdu_add(&tl->task, p);
conn_task_issue(c, &tl->task);
return;
done:
if (p)
pdu_free(p);
}
void
initiator_discovery_cb(struct connection *c, void *arg, struct pdu *p)
{
struct task *t = arg;
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:
size = lresp->datalen[0] << 16 | lresp->datalen[1] << 8 |
lresp->datalen[2];
if (size == 0) {
/* empty response */
session_shutdown(c->session);
break;
}
buf = pdu_getbuf(p, &n, PDU_DATA);
if (size > n || buf == NULL)
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);
session_shutdown(c->session);
break;
default:
log_debug("initiator_discovery_cb: unexpected message type %x",
ISCSI_PDU_OPCODE(lresp->opcode));
fail:
conn_fail(c);
pdu_free(p);
return;
}
conn_task_cleanup(c, t);
free(t);
pdu_free(p);
}
void
initiator_logout_cb(struct connection *c, void *arg, struct pdu *p)
{
struct task_logout *tl = arg;
struct iscsi_pdu_logout_response *loresp;
loresp = pdu_getbuf(p, NULL, PDU_HEADER);
log_debug("initiator_logout_cb: "
"response %d, Time2Wait %d, Time2Retain %d",
loresp->response, ntohs(loresp->time2wait),
ntohs(loresp->time2retain));
switch (loresp->response) {
case ISCSI_LOGOUT_RESP_SUCCESS:
if (tl->reason == ISCSI_LOGOUT_CLOSE_SESS) {
conn_fsm(c, CONN_EV_LOGGED_OUT);
session_fsm(c->session, SESS_EV_CLOSED, NULL, 0);
} else {
conn_fsm(tl->c, CONN_EV_LOGGED_OUT);
session_fsm(c->session, SESS_EV_CONN_CLOSED, tl->c, 0);
}
break;
case ISCSI_LOGOUT_RESP_UNKN_CID:
/* connection ID not found, retry will not help */
log_warnx("%s: logout failed, cid %d unknown, giving up\n",
tl->c->session->config.SessionName,
tl->c->cid);
conn_fsm(tl->c, CONN_EV_FREE);
break;
case ISCSI_LOGOUT_RESP_NO_SUPPORT:
case ISCSI_LOGOUT_RESP_ERROR:
default:
/* need to retry logout after loresp->time2wait secs */
conn_fail(tl->c);
pdu_free(p);
return;
}
conn_task_cleanup(c, &tl->task);
free(tl);
pdu_free(p);
}
char *
default_initiator_name(void)
{
char *s, hostname[HOST_NAME_MAX+1];
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;
}