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

File: [local] / src / usr.sbin / iscsid / connection.c (download)

Revision 1.21, Sat Dec 5 06:38:18 2015 UTC (8 years, 6 months ago) by mmcc
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, HEAD
Changes since 1.20: +2 -2 lines

strings.h -> string.h as necessary to prevent implicit declaration
warnings

/*	$OpenBSD: connection.c,v 1.21 2015/12/05 06:38:18 mmcc 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 <netinet/in.h>
#include <netinet/tcp.h>

#include <scsi/iscsi.h>

#include <errno.h>
#include <event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "iscsid.h"
#include "log.h"

void	conn_dispatch(int, short, void *);
void	conn_write_dispatch(int, short, void *);

int	c_do_connect(struct connection *, enum c_event);
int	c_do_login(struct connection *, enum c_event);
int	c_do_loggedin(struct connection *, enum c_event);
int	c_do_req_logout(struct connection *, enum c_event);
int	c_do_logout(struct connection *, enum c_event);
int	c_do_loggedout(struct connection *, enum c_event);
int	c_do_fail(struct connection *, enum c_event);
int	c_do_cleanup(struct connection *, enum c_event);

const char *conn_state(int);
const char *conn_event(enum c_event);

void
conn_new(struct session *s, struct connection_config *cc)
{
	struct connection *c;
	int nodelay = 1;

	if (!(c = calloc(1, sizeof(*c))))
		fatal("session_add_conn");

	c->fd = -1;
	c->state = CONN_FREE;
	c->session = s;
	c->cid = arc4random();
	c->config = *cc;
	c->mine = initiator_conn_defaults;
	c->mine.HeaderDigest = s->config.HeaderDigest;
	c->mine.DataDigest = s->config.DataDigest;
	c->his = iscsi_conn_defaults;
	c->active = iscsi_conn_defaults;

	TAILQ_INIT(&c->pdu_w);
	TAILQ_INIT(&c->tasks);
	TAILQ_INSERT_TAIL(&s->connections, c, entry);

	if (pdu_readbuf_set(&c->prbuf, PDU_READ_SIZE)) {
		log_warn("conn_new");
		conn_free(c);
		return;
	}

	/* create socket */
	c->fd = socket(c->config.TargetAddr.ss_family, SOCK_STREAM, 0);
	if (c->fd == -1) {
		log_warn("conn_new: socket");
		conn_free(c);
		return;
	}
	if (socket_setblockmode(c->fd, 1)) {
		log_warn("conn_new: socket_setblockmode");
		conn_free(c);
		return;
	}

	/* try to turn off TCP Nagle */
	if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &nodelay,
	    sizeof(nodelay)) == -1)
		log_warn("conn_new: setting TCP_NODELAY");

	event_set(&c->ev, c->fd, EV_READ|EV_PERSIST, conn_dispatch, c);
	event_set(&c->wev, c->fd, EV_WRITE, conn_write_dispatch, c);

	conn_fsm(c, CONN_EV_CONNECT);
}

void
conn_free(struct connection *c)
{
	log_debug("conn_free");

	pdu_readbuf_free(&c->prbuf);
	pdu_free_queue(&c->pdu_w);

	event_del(&c->ev);
	event_del(&c->wev);
	if (c->fd != -1)
		close(c->fd);

	taskq_cleanup(&c->tasks);

	TAILQ_REMOVE(&c->session->connections, c, entry);
	free(c);
}

void
conn_dispatch(int fd, short event, void *arg)
{
	struct connection *c = arg;
	ssize_t n;

	if (!(event & EV_READ)) {
		log_debug("spurious read call");
		return;
	}
	if ((n = pdu_read(c)) == -1) {
		if (errno == EAGAIN || errno == ENOBUFS ||
		    errno == EINTR)	/* try later */
			return;
		log_warn("pdu_read");
		conn_fsm(c, CONN_EV_FAIL);
		return;
	}
	if (n == 0) {    /* connection closed */
		conn_fsm(c, CONN_EV_CLOSED);
		return;
	}

	pdu_parse(c);
}

void
conn_write_dispatch(int fd, short event, void *arg)
{
	struct connection *c = arg;
	ssize_t n;
	int error;
	socklen_t len;

	if (!(event & EV_WRITE)) {
		log_debug("spurious write call");
		return;
	}

	switch (c->state) {
	case CONN_XPT_WAIT:
		len = sizeof(error);
		if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR,
		    &error, &len) == -1 || (errno = error)) {
			log_warn("connect to %s failed",
			    log_sockaddr(&c->config.TargetAddr));
			conn_fsm(c, CONN_EV_FAIL);
			return;
		}
		conn_fsm(c, CONN_EV_CONNECTED);
		break;
	default:
		if ((n = pdu_write(c)) == -1) {
			log_warn("pdu_write");
			conn_fsm(c, CONN_EV_FAIL);
			return;
		}
		if (n == 0) {    /* connection closed */
			conn_fsm(c, CONN_EV_CLOSED);
			return;
		}

		/* check if there is more to send */
		if (pdu_pending(c))
			event_add(&c->wev, NULL);
	}
}

void
conn_fail(struct connection *c)
{
	log_debug("conn_fail");
	conn_fsm(c, CONN_EV_FAIL);
}

int
conn_task_ready(struct connection *c)
{
	if ((c->state & CONN_RUNNING) && TAILQ_EMPTY(&c->tasks))
		return 1;
	return 0;
}

void
conn_task_issue(struct connection *c, struct task *t)
{
	TAILQ_INSERT_TAIL(&c->tasks, t, entry);
	conn_task_schedule(c);
}

void
conn_task_schedule(struct connection *c)
{
	struct task *t = TAILQ_FIRST(&c->tasks);
	struct pdu *p, *np;

	if (!t) {
		log_debug("conn_task_schedule: task is hiding");
		return;
	}

	/* move pdus to the write queue */
	for (p = TAILQ_FIRST(&t->sendq); p != NULL; p = np) {
		np = TAILQ_NEXT(p, entry);
		TAILQ_REMOVE(&t->sendq, p, entry);
		conn_pdu_write(c, p);
	}
	if (t->callback == NULL) {
		/* no callback, immediate command expecting no answer */
		conn_task_cleanup(c, t);
		free(t);
	}
}

void
conn_task_cleanup(struct connection *c, struct task *t)
{
	pdu_free_queue(&t->sendq);
	pdu_free_queue(&t->recvq);
	/* XXX need some state to know if queued or not */
	if (c) {
		TAILQ_REMOVE(&c->tasks, t, entry);
		if (!TAILQ_EMPTY(&c->tasks))
			conn_task_schedule(c);
		else
			session_schedule(c->session);
	}
}

#define SET_NUM(p, x, v, min, max)				\
do {								\
	if (!strcmp((p)->key, #v)) {				\
		(x)->his.v = text_to_num((p)->value, (min), (max), &err); \
		if (err) {					\
			log_warnx("bad param %s=%s: %s",	\
			    (p)->key, (p)->value, err);		\
			errors++;				\
		}						\
log_debug("SET_NUM: %s = %llu", #v, (u_int64_t)(x)->his.v);	\
	}							\
} while (0)

#define SET_BOOL(p, x, v)					\
do {								\
	if (!strcmp((p)->key, #v)) {				\
		(x)->his.v = text_to_bool((p)->value, &err);	\
		if (err) {					\
			log_warnx("bad param %s=%s: %s",	\
			    (p)->key, (p)->value, err);		\
			errors++;				\
		}						\
log_debug("SET_BOOL: %s = %u", #v, (int)(x)->his.v);		\
	}							\
} while (0)

int
conn_parse_kvp(struct connection *c, struct kvp *kvp)
{
	struct kvp *k;
	struct session *s = c->session;
	const char *err;
	int errors = 0;


	for (k = kvp; k->key; k++) {
log_debug("conn_parse_kvp: %s = %s", k->key, k->value);
		/* XXX handle NotUnderstood|Irrelevant|Reject */
		SET_NUM(k, s, MaxBurstLength, 512, 16777215);
		SET_NUM(k, s, FirstBurstLength, 512, 16777215);
		SET_NUM(k, s, DefaultTime2Wait, 0, 3600);
		SET_NUM(k, s, DefaultTime2Retain, 0, 3600);
		SET_NUM(k, s, MaxOutstandingR2T, 1, 65535);
		SET_NUM(k, s, TargetPortalGroupTag, 0, 65535);
		SET_NUM(k, s, MaxConnections, 1, 65535);
		SET_BOOL(k, s, InitialR2T);
		SET_BOOL(k, s, ImmediateData);
		SET_BOOL(k, s, DataPDUInOrder);
		SET_BOOL(k, s, DataSequenceInOrder);
		SET_NUM(k, s, ErrorRecoveryLevel, 0, 2);
		SET_NUM(k, c, MaxRecvDataSegmentLength, 512, 16777215);
	}

	if (errors) {
		log_warnx("conn_parse_kvp: errors found");
		return -1;
	}
	return 0;
}

#undef SET_NUM
#undef SET_BOOL

int
conn_gen_kvp(struct connection *c, struct kvp *kvp, size_t *nkvp)
{
	struct session *s = c->session;
	size_t i = 0;

	if (s->mine.MaxConnections != iscsi_sess_defaults.MaxConnections) {
		if (kvp && i < *nkvp) {
			kvp[i].key = strdup("MaxConnections");
			if (kvp[i].key == NULL)
				return -1;
			if (asprintf(&kvp[i].value, "%hu",
			    s->mine.MaxConnections) == -1) {
				kvp[i].value = NULL;
				return -1;
			}
		}
		i++;
	}
	if (c->mine.MaxRecvDataSegmentLength !=
	    iscsi_conn_defaults.MaxRecvDataSegmentLength) {
		if (kvp && i < *nkvp) {
			kvp[i].key = strdup("MaxRecvDataSegmentLength");
			if (kvp[i].key == NULL)
				return -1;
			if (asprintf(&kvp[i].value, "%u",
			    c->mine.MaxRecvDataSegmentLength) == -1) {
				kvp[i].value = NULL;
				return -1;
			}
		}
		i++;
	}

	*nkvp = i;
	return 0;
}

void
conn_pdu_write(struct connection *c, struct pdu *p)
{
	struct iscsi_pdu *ipdu;

/* XXX I GUESS THIS SHOULD BE MOVED TO PDU SOMEHOW... */
	ipdu = pdu_getbuf(p, NULL, PDU_HEADER);
	switch (ISCSI_PDU_OPCODE(ipdu->opcode)) {
	case ISCSI_OP_I_NOP:
	case ISCSI_OP_SCSI_REQUEST:
	case ISCSI_OP_TASK_REQUEST:
	case ISCSI_OP_LOGIN_REQUEST:
	case ISCSI_OP_TEXT_REQUEST:
	case ISCSI_OP_DATA_OUT:
	case ISCSI_OP_LOGOUT_REQUEST:
	case ISCSI_OP_SNACK_REQUEST:
		ipdu->expstatsn = ntohl(c->expstatsn);
		break;
	}

	TAILQ_INSERT_TAIL(&c->pdu_w, p, entry);
	event_add(&c->wev, NULL);
}

/* connection state machine more or less as specified in the RFC */
struct {
	int		state;
	enum c_event	event;
	int		(*action)(struct connection *, enum c_event);
} fsm[] = {
	{ CONN_FREE, CONN_EV_CONNECT, c_do_connect },		/* T1 */
	{ CONN_XPT_WAIT, CONN_EV_CONNECTED, c_do_login },	/* T4 */
	{ CONN_IN_LOGIN, CONN_EV_LOGGED_IN, c_do_loggedin },	/* T5 */
	{ CONN_LOGGED_IN, CONN_EV_LOGOUT, c_do_logout },	/* T9 */
	{ CONN_LOGGED_IN, CONN_EV_REQ_LOGOUT, c_do_req_logout },/* T11 */
	{ CONN_LOGOUT_REQ, CONN_EV_LOGOUT, c_do_logout },	/* T10 */
	{ CONN_LOGOUT_REQ, CONN_EV_REQ_LOGOUT, c_do_req_logout},/* T12 */
	{ CONN_LOGOUT_REQ, CONN_EV_LOGGED_OUT, c_do_loggedout },/* T18 */
	{ CONN_IN_LOGOUT, CONN_EV_LOGGED_OUT, c_do_loggedout },	/* T13 */
	{ CONN_IN_LOGOUT, CONN_EV_REQ_LOGOUT, c_do_req_logout },/* T14 */
	{ CONN_CLEANUP_WAIT, CONN_EV_CLEANING_UP, c_do_cleanup},/* M2 */
	{ CONN_CLEANUP_WAIT, CONN_EV_FREE, c_do_loggedout },	/* M1 */
	{ CONN_IN_CLEANUP, CONN_EV_FREE, c_do_loggedout },	/* M4 */
	{ CONN_IN_CLEANUP, CONN_EV_CLEANING_UP, c_do_cleanup},
	/* either one of T2, T7, T15, T16, T17, M3 */
	{ CONN_ANYSTATE, CONN_EV_CLOSED, c_do_fail },
	{ CONN_ANYSTATE, CONN_EV_FAIL, c_do_fail },
	{ CONN_ANYSTATE, CONN_EV_FREE, c_do_fail },
	{ 0, 0, NULL }
};

void
conn_fsm(struct connection *c, enum c_event event)
{
	int	i, ns;

	for (i = 0; fsm[i].action != NULL; i++) {
		if (c->state & fsm[i].state && event == fsm[i].event) {
			log_debug("conn_fsm[%s]: %s ev %s",
			    c->session->config.SessionName,
			    conn_state(c->state), conn_event(event));
			ns = fsm[i].action(c, event);
			if (ns == -1)
				/* XXX better please */
				fatalx("conn_fsm: action failed");
			log_debug("conn_fsm[%s]: new state %s",
			    c->session->config.SessionName, conn_state(ns));
			c->state = ns;
			return;
		}
	}
	log_warnx("conn_fsm[%s]: unhandled state transition [%s, %s]",
	    c->session->config.SessionName, conn_state(c->state),
	    conn_event(event));
	fatalx("bork bork bork");
}

int
c_do_connect(struct connection *c, enum c_event ev)
{
	if (c->fd == -1) {
		log_warnx("connect(%s), lost socket",
		    log_sockaddr(&c->config.TargetAddr));
		session_fsm(c->session, SESS_EV_CONN_FAIL, c, 0);
		return CONN_FREE;
	}
	if (c->config.LocalAddr.ss_len != 0) {
		if (bind(c->fd, (struct sockaddr *)&c->config.LocalAddr,
		    c->config.LocalAddr.ss_len) == -1) {
			log_warn("bind(%s)",
			    log_sockaddr(&c->config.LocalAddr));
			session_fsm(c->session, SESS_EV_CONN_FAIL, c, 0);
			return CONN_FREE;
		}
	}
	if (connect(c->fd, (struct sockaddr *)&c->config.TargetAddr,
	    c->config.TargetAddr.ss_len) == -1) {
		if (errno == EINPROGRESS) {
			event_add(&c->wev, NULL);
			event_add(&c->ev, NULL);
			return CONN_XPT_WAIT;
		} else {
			log_warn("connect(%s)",
			    log_sockaddr(&c->config.TargetAddr));
			session_fsm(c->session, SESS_EV_CONN_FAIL, c, 0);
			return CONN_FREE;
		}
	}
	event_add(&c->ev, NULL);
	/* move forward */
	return c_do_login(c, CONN_EV_CONNECTED);
}

int
c_do_login(struct connection *c, enum c_event ev)
{
	/* start a login session and hope for the best ... */
	initiator_login(c);
	return CONN_IN_LOGIN;
}

int
c_do_loggedin(struct connection *c, enum c_event ev)
{
	iscsi_merge_conn_params(&c->active, &c->mine, &c->his);
	session_fsm(c->session, SESS_EV_CONN_LOGGED_IN, c, 0);

	return CONN_LOGGED_IN;
}

int
c_do_req_logout(struct connection *c, enum c_event ev)
{
	/* target requested logout. XXX implement async handler */

	if (c->state & CONN_IN_LOGOUT)
		return CONN_IN_LOGOUT;
	else
		return CONN_LOGOUT_REQ;
}

int
c_do_logout(struct connection *c, enum c_event ev)
{
	/* logout is in progress ... */
	return CONN_IN_LOGOUT;
}

int
c_do_loggedout(struct connection *c, enum c_event ev)
{
	/*
	 * Called by the session fsm before calling conn_free.
	 * Doing this so the state transition is logged.
	 */
	return CONN_FREE;
}

int
c_do_fail(struct connection *c, enum c_event ev)
{
	log_debug("c_do_fail");

	/* cleanup events so that the connection does not retrigger */
	event_del(&c->ev);
	event_del(&c->wev);
	close(c->fd);
	c->fd = -1;	/* make sure this fd is not closed again */

	/* all pending task have failed so clean them up */
	taskq_cleanup(&c->tasks);

	/* session will take care of cleaning up the mess */
	session_fsm(c->session, SESS_EV_CONN_FAIL, c, 0);

	if (ev == CONN_EV_FREE || c->state & CONN_NEVER_LOGGED_IN)
		return CONN_FREE;
	return CONN_CLEANUP_WAIT;
}

int
c_do_cleanup(struct connection *c, enum c_event ev)
{
	/* nothing to do here just adjust state */
	return CONN_IN_CLEANUP;
}

const char *
conn_state(int s)
{
	static char buf[15];

	switch (s) {
	case CONN_FREE:
		return "FREE";
	case CONN_XPT_WAIT:
		return "XPT_WAIT";
	case CONN_XPT_UP:
		return "XPT_UP";
	case CONN_IN_LOGIN:
		return "IN_LOGIN";
	case CONN_LOGGED_IN:
		return "LOGGED_IN";
	case CONN_IN_LOGOUT:
		return "IN_LOGOUT";
	case CONN_LOGOUT_REQ:
		return "LOGOUT_REQ";
	case CONN_CLEANUP_WAIT:
		return "CLEANUP_WAIT";
	case CONN_IN_CLEANUP:
		return "IN_CLEANUP";
	default:
		snprintf(buf, sizeof(buf), "UKNWN %x", s);
		return buf;
	}
	/* NOTREACHED */
}

const char *
conn_event(enum c_event e)
{
	static char buf[15];

	switch (e) {
	case CONN_EV_FAIL:
		return "fail";
	case CONN_EV_CONNECT:
		return "connect";
	case CONN_EV_CONNECTED:
		return "connected";
	case CONN_EV_LOGGED_IN:
		return "logged in";
	case CONN_EV_REQ_LOGOUT:
		return "logout requested";
	case CONN_EV_LOGOUT:
		return "logout";
	case CONN_EV_LOGGED_OUT:
		return "logged out";
	case CONN_EV_CLEANING_UP:
		return "cleaning up";
	case CONN_EV_CLOSED:
		return "closed";
	case CONN_EV_FREE:
		return "forced free";
	}

	snprintf(buf, sizeof(buf), "UKNWN %d", e);
	return buf;
}