[BACK]Return to initiator.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / iscsid

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;
}