diff options
-rw-r--r-- | usr.sbin/iscsid/Makefile | 18 | ||||
-rw-r--r-- | usr.sbin/iscsid/connection.c | 379 | ||||
-rw-r--r-- | usr.sbin/iscsid/control.c | 327 | ||||
-rw-r--r-- | usr.sbin/iscsid/initiator.c | 409 | ||||
-rw-r--r-- | usr.sbin/iscsid/iscsid.c | 226 | ||||
-rw-r--r-- | usr.sbin/iscsid/iscsid.h | 297 | ||||
-rw-r--r-- | usr.sbin/iscsid/log.c | 225 | ||||
-rw-r--r-- | usr.sbin/iscsid/log.h | 35 | ||||
-rw-r--r-- | usr.sbin/iscsid/pdu.c | 371 | ||||
-rw-r--r-- | usr.sbin/iscsid/task.c | 124 | ||||
-rw-r--r-- | usr.sbin/iscsid/util.c | 143 | ||||
-rw-r--r-- | usr.sbin/iscsid/vscsi.c | 286 |
12 files changed, 2840 insertions, 0 deletions
diff --git a/usr.sbin/iscsid/Makefile b/usr.sbin/iscsid/Makefile new file mode 100644 index 00000000000..e51fae3a33f --- /dev/null +++ b/usr.sbin/iscsid/Makefile @@ -0,0 +1,18 @@ +# $OpenBSD: Makefile,v 1.1 2010/09/24 09:43:19 claudio Exp $ + +PROG= iscsid +SRCS= connection.c control.c initiator.c iscsid.c log.c pdu.c task.c \ + util.c vscsi.c + +#MAN= iscsid.8 + +DEBUG=-g +CFLAGS+= -Wall +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +LDADD+= -levent +DPADD+= ${LIBEVENT} + +.include <bsd.prog.mk> diff --git a/usr.sbin/iscsid/connection.c b/usr.sbin/iscsid/connection.c new file mode 100644 index 00000000000..3bf6bb653be --- /dev/null +++ b/usr.sbin/iscsid/connection.c @@ -0,0 +1,379 @@ +/* $OpenBSD: connection.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/types.h> +#include <sys/ioctl.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <netinet/in.h> + +#include <scsi/iscsi.h> +#include <scsi/scsi_all.h> +#include <dev/vscsivar.h> + +#include <errno.h> +#include <event.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "iscsid.h" +#include "log.h" + +void conn_fsm(struct connection *, enum c_event); +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_logout(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; + + 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; + 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; + } + + 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); + event_add(&c->ev, NULL); + + conn_fsm(c, CONN_EV_CONNECT); +} + +void +conn_free(struct connection *c) +{ + pdu_readbuf_free(&c->prbuf); + pdu_free_queue(&c->pdu_w); + + event_del(&c->ev); + event_del(&c->wev); + close(c->fd); + + 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) { + conn_fail(c); + return; + } + if (n == 0) { /* connection closed */ + conn_fsm(c, CONN_EV_CLOSE); + 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("cwd connect(%s)", + log_sockaddr(&c->config.TargetAddr)); + conn_fail(c); + return; + } + conn_fsm(c, CONN_EV_CONNECTED); + break; + default: + if ((n = pdu_write(c)) == -1) { + conn_fail(c); + return; + } + if (n == 0) { /* connection closed */ + conn_fsm(c, CONN_EV_CLOSE); + return; + } + + /* check if there is more to send */ + if (pdu_pending(c)) + event_add(&c->wev, NULL); + } +} + +void +conn_close(struct connection *c) +{ + conn_fsm(c, CONN_EV_CLOSE); +} + +void +conn_fail(struct connection *c) +{ + conn_fsm(c, CONN_EV_FAIL); +} + +void +conn_loggedin(struct connection *c) +{ + if (c->session->config.SessionType == SESSION_TYPE_DISCOVERY) + conn_fsm(c, CONN_EV_DISCOVERY); + else + conn_fsm(c, CONN_EV_LOGGED_IN); +} + +int +conn_task_issue(struct connection *c, struct task *t) +{ + /* XXX need to verify that we're in the right state for the task */ + +log_debug("conn_task_issue"); + if (!TAILQ_EMPTY(&c->tasks)) + return 0; + + TAILQ_INSERT_TAIL(&c->tasks, t, entry); + conn_task_schedule(c); + return 1; +} + +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); /* maybe inline ? */ + } +} + +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_TASK_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 }, + { CONN_XPT_WAIT, CONN_EV_CONNECTED, c_do_login }, + { CONN_IN_LOGIN, CONN_EV_LOGGED_IN, c_do_loggedin }, + { CONN_IN_LOGIN, CONN_EV_DISCOVERY, c_do_loggedin }, + { CONN_LOGGED_IN, CONN_EV_CLOSE, c_do_logout }, + { 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) { + ns = fsm[i].action(c, event); + log_debug("conn_fsm: %s ev %s -> %s", + conn_state(c->state), conn_event(event), + conn_state(ns)); + if (ns == -1) + /* XXX better please */ + fatalx("conn_fsm: action failed"); + c->state = ns; + return; + } + } + log_warnx("conn_fsm: unhandled state transition [%s, %s]", + 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) + fatalx("c_do_connect, lost socket"); + + if (connect(c->fd, (struct sockaddr *)&c->config.TargetAddr, + c->config.TargetAddr.ss_len) == -1) { + if (errno == EINPROGRESS) { + event_add(&c->wev, NULL); + return (CONN_XPT_WAIT); + } else { + log_warn("connect(%s)", + log_sockaddr(&c->config.TargetAddr)); + return (-1); + } + } + /* 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) +{ + if (ev == CONN_EV_LOGGED_IN) + vscsi_event(VSCSI_REQPROBE, c->session->target, 0); + else + initiator_discovery(c->session); + return (CONN_LOGGED_IN); +} + +int +c_do_logout(struct connection *c, enum c_event ev) +{ + /* do full logout */ + return (CONN_FREE); +} + +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_DISCOVERY: + return "discovery"; + case CONN_EV_CLOSE: + return "close"; + } + + snprintf(buf, sizeof(buf), "UKNWN %d", e); + return buf; +} diff --git a/usr.sbin/iscsid/control.c b/usr.sbin/iscsid/control.c new file mode 100644 index 00000000000..e95b1a45e81 --- /dev/null +++ b/usr.sbin/iscsid/control.c @@ -0,0 +1,327 @@ +/* $OpenBSD: control.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */ + +/* + * Copyright (c) 2010 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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/stat.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/un.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "iscsid.h" +#include "log.h" + +struct control { + TAILQ_ENTRY(control) entry; + struct event ev; + struct pduq channel; + int fd; +} *control_state; + +TAILQ_HEAD(, control) controls; + +#define CONTROL_BACKLOG 5 + +void control_accept(int, short, void *); +void control_close(struct control *); +void control_dispatch(int, short, void *); +struct pdu *control_getpdu(char *, size_t); + +int +control_init(char *path) +{ + struct sockaddr_un sun; + int fd; + mode_t old_umask; + + if ((control_state = calloc(1, sizeof(*control_state))) == NULL) { + log_warn("control_init: calloc"); + return (-1); + } + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + log_warn("control_init: socket"); + return (-1); + } + + bzero(&sun, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + + if (unlink(path) == -1) + if (errno != ENOENT) { + log_warn("control_init: unlink %s", path); + close(fd); + return (-1); + } + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + log_warn("control_init: bind: %s", path); + close(fd); + umask(old_umask); + return (-1); + } + umask(old_umask); + + if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("control_init: chmod"); + close(fd); + (void)unlink(path); + return (-1); + } + + socket_setblockmode(fd, 1); + control_state->fd = fd; + TAILQ_INIT(&control_state->channel); + TAILQ_INIT(&controls); + + return (0); +} + +void +control_cleanup(char *path) +{ + struct control *c; + + if (path) + unlink(path); + + while ((c = TAILQ_FIRST(&controls)) != NULL) { + TAILQ_REMOVE(&controls, c, entry); + control_close(c); + } + control_close(control_state); +} + +int +control_listen(void) +{ + if (listen(control_state->fd, CONTROL_BACKLOG) == -1) { + log_warn("control_listen: listen"); + return (-1); + } + + event_set(&control_state->ev, control_state->fd, EV_READ | EV_PERSIST, + control_accept, NULL); + event_add(&control_state->ev, NULL); + + return (0); +} + +/* ARGSUSED */ +void +control_accept(int listenfd, short event, void *bula) +{ + int connfd; + socklen_t len; + struct sockaddr_un sun; + struct control *c; + + len = sizeof(sun); + if ((connfd = accept(listenfd, + (struct sockaddr *)&sun, &len)) == -1) { + if (errno != EWOULDBLOCK && errno != EINTR) + log_warn("control_accept"); + return; + } + + if ((c = malloc(sizeof(struct control))) == NULL) { + log_warn("control_accept"); + close(connfd); + return; + } + + TAILQ_INIT(&c->channel); + c->fd = connfd; + event_set(&c->ev, connfd, EV_READ, control_dispatch, c); + event_add(&c->ev, NULL); +} + +void +control_close(struct control *c) +{ + close(c->fd); + event_del(&c->ev); + pdu_free_queue(&c->channel); + free(c); +} + +static char cbuf[CONTROL_READ_SIZE]; + +/* ARGSUSED */ +void +control_dispatch(int fd, short event, void *bula) +{ + struct iovec iov[PDU_MAXIOV]; + struct msghdr msg; + struct control *c = bula; + struct pdu *pdu; + ssize_t n; + unsigned int niov = 0; + short flags = EV_READ; + + if (event & EV_TIMEOUT) { + log_debug("control connection (fd %d) timed out.", fd); + control_close(c); + return; + } + if (event & EV_READ) { + if ((n = recv(fd, cbuf, sizeof(cbuf), 0)) == -1 && + !(errno == EAGAIN || errno == EINTR)) { + control_close(c); + return; + } + if (n == 0) { + control_close(c); + return; + } +log_debug("control_dispatch: recv %zd bytes", n); + pdu = control_getpdu(cbuf, n); + if (!pdu) { + log_debug("control connection (fd %d) bad msg.", fd); + control_close(c); + return; + } + iscsid_ctrl_dispatch(c, pdu); + } + if (event & EV_WRITE) { + if ((pdu = TAILQ_FIRST(&c->channel)) != NULL) { + TAILQ_REMOVE(&c->channel, pdu, entry); + + for (niov = 0; niov < PDU_MAXIOV; niov++) { + iov[niov].iov_base = pdu->iov[niov].iov_base; + iov[niov].iov_len = pdu->iov[niov].iov_len; + } + bzero(&msg, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = niov; +log_debug("control_dispatch: send %d iov", niov); + if (sendmsg(fd, &msg, 0) == -1 && + !(errno == EAGAIN || errno == ENOBUFS)) { + control_close(c); + return; + } + } + } + if (!TAILQ_EMPTY(&c->channel)) + flags |= EV_WRITE; + + event_del(&c->ev); + event_set(&c->ev, fd, flags, control_dispatch, c); + event_add(&c->ev, NULL); +} + +struct pdu * +control_getpdu(char *buf, size_t len) +{ + struct pdu *p; + struct ctrlmsghdr *cmh; + void *data; + size_t n; + int i; + + if (len < sizeof(*cmh)) + return NULL; + + if (!(p = pdu_new())) + return NULL; + + n = sizeof(*cmh); + cmh = pdu_alloc(n); + bcopy(buf, cmh, n); + buf += n; + len -= n; + + if (pdu_addbuf(p, cmh, n, 0)) { + free(cmh); +fail: + pdu_free(p); + return NULL; + } + + for (i = 0; i < 3; i++) { + n = cmh->len[i]; + if (n == 0) + continue; + if (PDU_LEN(n) > len) + goto fail; + if (!(data = pdu_alloc(n))) + goto fail; + bcopy(buf, data, n); + if (pdu_addbuf(p, data, n, i + 1)) { + free(data); + goto fail; + } + buf += PDU_LEN(n); + len -= PDU_LEN(n); + } + + return p; +} + +int +control_queue(void *ch, struct pdu *pdu) +{ + struct control *c = ch; + + TAILQ_INSERT_TAIL(&c->channel, pdu, entry); + + event_del(&c->ev); + event_set(&c->ev, c->fd, EV_READ|EV_WRITE, control_dispatch, c); + event_add(&c->ev, NULL); + + return 0; +} + +int +control_compose(void *ch, u_int16_t type, void *buf, size_t len) +{ + struct pdu *pdu; + struct ctrlmsghdr *cmh; + void *ptr; + + if (PDU_LEN(len) > CONTROL_READ_SIZE - PDU_LEN(sizeof(*cmh))) + return -1; + if ((pdu = pdu_new()) == NULL) + return -1; + if ((cmh = pdu_alloc(sizeof(*cmh))) == NULL) + goto fail; + bzero(cmh, sizeof(*cmh)); + cmh->type = type; + cmh->len[0] = len; + pdu_addbuf(pdu, cmh, sizeof(*cmh), 0); + if (len > 0) { + if ((ptr = pdu_alloc(len)) == NULL) + goto fail; + bcopy(buf, ptr, len); + pdu_addbuf(pdu, ptr, len, 1); + } + + return control_queue(ch, pdu); +fail: + pdu_free(pdu); + return -1; +} 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; +} diff --git a/usr.sbin/iscsid/iscsid.c b/usr.sbin/iscsid/iscsid.c new file mode 100644 index 00000000000..8d226550256 --- /dev/null +++ b/usr.sbin/iscsid/iscsid.c @@ -0,0 +1,226 @@ +/* $OpenBSD: iscsid.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/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/uio.h> + +#include <err.h> +#include <event.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "iscsid.h" +#include "log.h" + +void main_sig_handler(int, short, void *); +__dead void usage(void); + +struct initiator *initiator; + +int +main(int argc, char *argv[]) +{ + struct event ev_sigint, ev_sigterm, ev_sighup; + struct passwd *pw; + char *ctrlsock = ISCSID_CONTROL; + char *vscsidev = ISCSID_DEVICE; + int ch, debug = 0; + + log_init(1); /* log to stderr until daemonized */ + + while ((ch = getopt(argc, argv, "dn:s:")) != -1) { + switch (ch) { + case 'd': + debug = 1; + break; + case 'n': + vscsidev = optarg; + break; + case 's': + ctrlsock = optarg; + break; + default: + usage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (argc > 0) + usage(); + + /* check for root privileges */ + if (geteuid()) + errx(1, "need root privileges"); + + log_init(debug); + if (!debug) + daemon(1, 0); + log_info("startup"); + + event_init(); + vscsi_open(vscsidev); + if (control_init(ctrlsock) == -1) + fatalx("control socket setup failed"); + + /* chroot and drop to iscsid user */ + if ((pw = getpwnam(ISCSID_USER)) == NULL) + errx(1, "unknown user %s", ISCSID_USER); + + if (chroot(pw->pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + + /* setup signal handler */ + signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL); + signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sighup, NULL); + signal(SIGPIPE, SIG_IGN); + + if (control_listen() == -1) + fatalx("control socket listen failed"); + + initiator = initiator_init(); + + event_dispatch(); + + /* CLEANUP XXX */ + control_cleanup(ctrlsock); + initiator_cleanup(initiator); + log_info("exiting."); + return 0; +} + +/* ARGSUSED */ +void +main_sig_handler(int sig, short event, void *arg) +{ + /* signal handler rules don't apply, libevent decouples for us */ + switch (sig) { + case SIGTERM: + case SIGINT: + case SIGHUP: + event_loopexit(NULL); + break; + default: + fatalx("unexpected signal"); + /* NOTREACHED */ + } +} + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-d] [-n vscsi] [-s socket]\n", + __progname); + exit(1); +} + +void +iscsid_ctrl_dispatch(void *ch, struct pdu *pdu) +{ + struct ctrlmsghdr *cmh; + struct initiator_config *ic; + struct session_config *sc; + struct session *s; + + cmh = pdu_getbuf(pdu, NULL, 0); + if (cmh == NULL) + goto done; + + switch (cmh->type) { + case CTRL_INITIATOR_CONFIG: +log_debug("CTRL_INITIATOR_CONFIG"); + if (cmh->len[0] != sizeof(*ic)) { + log_warnx("CTRL_INITIATOR_CONFIG bad size"); + control_compose(ch, CTRL_FAILURE, NULL, 0); + break; + } + ic = pdu_getbuf(pdu, NULL, 1); + bcopy(ic, &initiator->config, sizeof(initiator->config)); + control_compose(ch, CTRL_SUCCESS, NULL, 0); + break; + case CTRL_SESSION_CONFIG: +log_debug("CTRL_SESSION_CONFIG"); + if (cmh->len[0] != sizeof(*sc)) { + log_warnx("CTRL_INITIATOR_CONFIG bad size"); + control_compose(ch, CTRL_FAILURE, NULL, 0); + break; + } + sc = pdu_getbuf(pdu, NULL, 1); + if (cmh->len[1]) + sc->TargetName = pdu_getbuf(pdu, NULL, 2); + else if (sc->SessionType != SESSION_TYPE_DISCOVERY) { +log_debug("no TargetName but not discovery"); + control_compose(ch, CTRL_FAILURE, NULL, 0); + goto done; + } else + sc->TargetName = NULL; + if (cmh->len[2]) + sc->InitiatorName = pdu_getbuf(pdu, NULL, 3); + else + sc->InitiatorName = NULL; + +log_debug("session %s to %s", sc->SessionName, log_sockaddr(&sc->connection.TargetAddr)); + s = session_find(initiator, sc->SessionName); + if (s == NULL) { + s = session_new(initiator, sc->SessionType); + if (s == NULL) { + control_compose(ch, CTRL_FAILURE, NULL, 0); + goto done; + } + } + + session_config(s, sc); + + if (s->state == SESS_FREE) { + log_debug("new connection to %s", + log_sockaddr(&s->config.connection.TargetAddr)); + conn_new(s, &s->config.connection); + } + + control_compose(ch, CTRL_SUCCESS, NULL, 0); + break; + default: + log_warnx("unknown control message type %d", cmh->type); + control_compose(ch, CTRL_FAILURE, NULL, 0); + break; + } + +done: + pdu_free(pdu); +} diff --git a/usr.sbin/iscsid/iscsid.h b/usr.sbin/iscsid/iscsid.h new file mode 100644 index 00000000000..1e3ebe42176 --- /dev/null +++ b/usr.sbin/iscsid/iscsid.h @@ -0,0 +1,297 @@ +/* $OpenBSD: iscsid.h,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. + */ + +#define ISCSID_DEVICE "/dev/vscsi0" +#define ISCSID_CONTROL "/var/run/iscsid.sock" +#define ISCSID_CONFIG "/etc/iscsi.conf" +#define ISCSID_USER "_iscsid" + +#define ISCSID_BASE_NAME "iqn.1995-11.org.openbsd.iscsid" + +#define PDU_READ_SIZE (256 * 1024) +#define CONTROL_READ_SIZE 8192 +#define PDU_MAXIOV 5 +#define PDU_WRIOV (PDU_MAXIOV * 8) + +#define PDU_HEADER 0 +#define PDU_AHS 1 +#define PDU_HDIGEST 2 +#define PDU_DATA 3 +#define PDU_DDIGEST 4 + +#define PDU_LEN(x) ((((x) + 3) / 4) * 4) + +/* + * Common control message header. + * A message can consist of up to 3 parts with specified length. + */ +struct ctrlmsghdr { + u_int16_t type; + u_int16_t len[3]; +}; + +/* Control message types */ +#define CTRL_SUCCESS 1 +#define CTRL_FAILURE 2 +#define CTRL_INITIATOR_CONFIG 3 +#define CTRL_SESSION_CONFIG 4 + + +TAILQ_HEAD(session_head, session); +TAILQ_HEAD(connection_head, connection); +TAILQ_HEAD(pduq, pdu); +TAILQ_HEAD(taskq, task); + +/* as in tcp_seq.h */ +#define SEQ_LT(a,b) ((int)((a)-(b)) < 0) +#define SEQ_LEQ(a,b) ((int)((a)-(b)) <= 0) +#define SEQ_GT(a,b) ((int)((a)-(b)) > 0) +#define SEQ_GEQ(a,b) ((int)((a)-(b)) >= 0) + +#define SESS_FREE 0x0001 +#define SESS_LOGGED_IN 0x0002 +#define SESS_FAILED 0x0003 + +#define CONN_FREE 0x0001 +#define CONN_XPT_WAIT 0x0002 +#define CONN_XPT_UP 0x0004 +#define CONN_IN_LOGIN 0x0008 +#define CONN_LOGGED_IN 0x0010 +#define CONN_IN_LOGOUT 0x0020 +#define CONN_LOGOUT_REQ 0x0040 +#define CONN_CLEANUP_WAIT 0x0080 +#define CONN_IN_CLEANUP 0x0100 +#define CONN_ANYSTATE 0xffff + +enum c_event { + CONN_EV_FAIL, + CONN_EV_CONNECT, + CONN_EV_CONNECTED, + CONN_EV_LOGGED_IN, + CONN_EV_DISCOVERY, + CONN_EV_CLOSE +}; + +struct pdu { + TAILQ_ENTRY(pdu) entry; + struct iovec iov[PDU_MAXIOV]; + size_t resid; +}; + +struct pdu_readbuf { + char *buf; + size_t size; + size_t rpos; + size_t wpos; + struct pdu *wip; +}; + +struct connection_config { + /* values inherited from session_config */ + struct sockaddr_storage TargetAddr; /* IP:port of target */ + struct sockaddr_storage LocalAddr; /* IP:port of target */ +}; + +struct initiator_config { + u_int32_t isid_base; /* only 24 bits */ + u_int16_t isid_qual; + u_int16_t pad; +}; + +struct session_config { + /* unique session ID */ + char SessionName[32]; + /* + * I = initialize only, L = leading only + * S = session wide, C = connection only + */ + struct connection_config connection; + + char *TargetName; /* String: IS */ + + char *InitiatorName; /* String: IS */ + + u_int16_t MaxConnections; + /* 1, 1-65535 (min()): LS */ + u_int8_t HeaderDigest; + /* None , (None|CRC32): IC */ + u_int8_t DataDigest; + /* None , (None|CRC32): IC */ + u_int8_t SessionType; + /* Normal, (Discovery|Normal): LS */ + u_int8_t disabled; +}; + +#define SESSION_TYPE_NORMAL 0 +#define SESSION_TYPE_DISCOVERY 1 + +struct session_params { + u_int32_t MaxBurstLength; + /* 262144, 512-to-(2**24-1) (min()): LS */ + u_int32_t FirstBurstLength; + /* 65536, 512-to-(2**24-1) (min()): LS */ + u_int16_t DefaultTime2Wait; + /* 2, 0-to-3600 (max()): LS */ + u_int16_t DefaultTime2Retain; + /* 20, 0-to-3600 (min()): LS */ + u_int16_t MaxOutstandingR2T; + /* 1, 1-to-65535 (min()): LS */ + u_int16_t TargetPortalGroupTag; + /* 1- 65535: IS */ + u_int8_t InitialR2T; + /* yes, bool (||): LS */ + u_int8_t ImmediateData; + /* yes, bool (&&): LS */ + u_int8_t DataPDUInOrder; + /* yes, bool (||): LS */ + u_int8_t DataSequenceInOrder; + /* yes, bool (||): LS */ + u_int8_t ErrorRecoveryLevel; + /* 0, 0 - 2 (min()): LS */ +}; + +struct connection_params { + u_int32_t MaxRecvDataSegmentLength; + /* 8192, 512-to-(2**24-1): C */ + /* inherited from session_config */ + u_int8_t HeaderDigest; + u_int8_t DataDigest; +}; + +struct initiator { + struct session_head sessions; + struct initiator_config config; + u_int target; +}; + +struct session { + TAILQ_ENTRY(session) entry; + struct connection_head connections; + struct taskq tasks; + struct session_config config; + struct session_params mine; + struct session_params his; + struct session_params active; + struct initiator *initiator; + u_int32_t cmdseqnum; + u_int32_t itt; + u_int32_t isid_base; /* only 24 bits */ + u_int16_t isid_qual; /* inherited from initiator */ + u_int16_t tsih; /* target session id handle */ + u_int target; + int state; +}; + +struct connection { + struct event ev; + struct event wev; + TAILQ_ENTRY(connection) entry; + struct connection_params mine; + struct connection_params his; + struct connection_params active; + struct connection_config config; + struct pdu_readbuf prbuf; + struct pduq pdu_w; + struct taskq tasks; + struct session *session; + u_int32_t expstatsn; + int state; + int fd; + u_int16_t cid; /* conection id */ +}; + +struct task { + TAILQ_ENTRY(task) entry; + struct pduq sendq; + struct pduq recvq; + void *callarg; + void (*callback)(struct connection *, void *, struct pdu *); + u_int32_t cmdseqnum; + u_int32_t itt; +}; + +struct kvp { + char *key; + char *value; + long flags; +}; +#define KVP_KEY_ALLOCED 0x01 +#define KVP_VALUE_ALLOCED 0x02 + +void iscsid_ctrl_dispatch(void *, struct pdu *); + +struct initiator *initiator_init(void); +void initiator_cleanup(struct initiator *); +struct session *initiator_t2s(u_int); + +int control_init(char *); +void control_cleanup(char *); +int control_listen(void); +int control_queue(void *, struct pdu *); +int control_compose(void *, u_int16_t, void *, size_t); + +struct session *session_find(struct initiator *, char *); +struct session *session_new(struct initiator *, u_int8_t); +void session_close(struct session *); +void session_config(struct session *, struct session_config *); +void session_task_issue(struct session *, struct task *); +void session_schedule(struct session *); +void session_task_login(struct connection *); +void initiator_login(struct connection *); +void initiator_discovery(struct session *); + +void conn_new(struct session *, struct connection_config *); +void conn_free(struct connection *); +int conn_task_issue(struct connection *, struct task *); +void conn_task_schedule(struct connection *); +void conn_pdu_write(struct connection *, struct pdu *); +void conn_close(struct connection *); +void conn_fail(struct connection *); +void conn_loggedin(struct connection *); + +void *pdu_gethdr(struct pdu *); +int text_to_pdu(struct kvp *, struct pdu *); +struct kvp *pdu_to_text(char *, size_t); +void pdu_free_queue(struct pduq *); +ssize_t pdu_read(struct connection *); +ssize_t pdu_write(struct connection *); +int pdu_pending(struct connection *); +void pdu_parse(struct connection *); +int pdu_readbuf_set(struct pdu_readbuf *, size_t); +void pdu_readbuf_free(struct pdu_readbuf *); + +struct pdu *pdu_new(void); +void *pdu_alloc(size_t); +void *pdu_dup(void *, size_t); +int pdu_addbuf(struct pdu *, void *, size_t, unsigned int); +void *pdu_getbuf(struct pdu *, size_t *, unsigned int); +void pdu_free(struct pdu *); +int socket_setblockmode(int, int); +const char *log_sockaddr(void *); + +void task_init(struct task *, struct session *, int, void *, + void (*)(struct connection *, void *, struct pdu *)); +void task_cleanup(struct task *, struct connection *c); +void task_pdu_add(struct task *, struct pdu *); +void task_pdu_cb(struct connection *, struct pdu *); + +void vscsi_open(char *); +void vscsi_dispatch(int, short, void *); +void vscsi_data(unsigned long, int, void *, size_t); +void vscsi_status(int, int, void *, size_t); +void vscsi_event(unsigned long, u_int, u_int); diff --git a/usr.sbin/iscsid/log.c b/usr.sbin/iscsid/log.c new file mode 100644 index 00000000000..088cb207775 --- /dev/null +++ b/usr.sbin/iscsid/log.c @@ -0,0 +1,225 @@ +/* $OpenBSD: log.c,v 1.1 2010/09/24 09:43:19 claudio Exp $ */ + +/* + * Copyright (c) 2009 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@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 <errno.h> +#include <event.h> +#include <netdb.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "iscsid.h" +#include "log.h" + +int debug; + +void logit(int, const char *, ...); + +void +log_init(int n_debug) +{ + extern char *__progname; + + debug = n_debug; + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + + tzset(); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); +} + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_CRIT, "%s", strerror(errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { + /* we tried it... */ + vlog(LOG_CRIT, emsg, ap); + logit(LOG_CRIT, "%s", strerror(errno)); + } else { + vlog(LOG_CRIT, nfmt, ap); + free(nfmt); + } + va_end(ap); + } +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_CRIT, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (debug) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +fatal(const char *emsg) +{ + if (emsg == NULL) + logit(LOG_CRIT, "fatal: %s", strerror(errno)); + else + if (errno) + logit(LOG_CRIT, "fatal: %s: %s", emsg, strerror(errno)); + else + logit(LOG_CRIT, "fatal: %s", emsg); + + exit(1); +} + +void +fatalx(const char *emsg) +{ + errno = 0; + fatal(emsg); +} + +void +log_hexdump(void *buf, size_t len) +{ + u_char b[16]; + size_t i, j, l; + + if (!debug) + return; + + for (i = 0; i < len; i += l) { + fprintf(stderr, "%4zi:", i); + l = sizeof(b) < len - i ? sizeof(b) : len - i; + bcopy((char *)buf + i, b, l); + + for (j = 0; j < sizeof(b); j++) { + if (j % 2 == 0) + fprintf(stderr, " "); + if (j % 8 == 0) + fprintf(stderr, " "); + if (j < l) + fprintf(stderr, "%02x", (int)b[j]); + else + fprintf(stderr, " "); + } + fprintf(stderr, " |"); + for (j = 0; j < l; j++) { + if (b[j] >= 0x20 && b[j] <= 0x7e) + fprintf(stderr, "%c", b[j]); + else + fprintf(stderr, "."); + } + fprintf(stderr, "|\n"); + } +} + +void +log_pdu(struct pdu *p, int all) +{ + struct iscsi_pdu *pdu; + void *b; + size_t s; + + if (!debug) + return; + + if (!(pdu = pdu_getbuf(p, NULL, PDU_HEADER))) { + log_debug("empty pdu"); + return; + } + + fprintf(stderr, "PDU: op %x%s flags %02x%02x%02x ahs %d len %d\n", + ISCSI_PDU_OPCODE(pdu->opcode), ISCSI_PDU_I(pdu) ? " I" : "", + pdu->flags, pdu->_reserved1[0], pdu->_reserved1[1], + pdu->ahslen, pdu->datalen[0] << 16 | pdu->datalen[1] << 8 | + pdu->datalen[2]); + fprintf(stderr, " lun %02x%02x%02x%02x%02x%02x%02x%02x itt %d " + "cmdsn %d expstatsn %d\n", pdu->lun[0], pdu->lun[1], pdu->lun[2], + pdu->lun[3], pdu->lun[4], pdu->lun[5], pdu->lun[6], pdu->lun[7], + ntohl(pdu->itt), ntohl(pdu->cmdsn), ntohl(pdu->expstatsn)); + log_hexdump(pdu, sizeof(*pdu)); + + if (all && (b = pdu_getbuf(p, &s, PDU_DATA))) + log_hexdump(b, s); +} diff --git a/usr.sbin/iscsid/log.h b/usr.sbin/iscsid/log.h new file mode 100644 index 00000000000..a8658ea2223 --- /dev/null +++ b/usr.sbin/iscsid/log.h @@ -0,0 +1,35 @@ +/* $OpenBSD: log.h,v 1.1 2010/09/24 09:43:19 claudio Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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. + */ + +#ifndef _LOG_H_ +#define _LOG_H_ + +#include <stdarg.h> + +void log_init(int); +void vlog(int, const char *, va_list); +void log_warn(const char *, ...); +void log_warnx(const char *, ...); +void log_info(const char *, ...); +void log_debug(const char *, ...); +void fatal(const char *) __dead; +void fatalx(const char *) __dead; +void log_hexdump(void *, size_t); +void log_pdu(struct pdu *, int); + +#endif /* _LOG_H_ */ diff --git a/usr.sbin/iscsid/pdu.c b/usr.sbin/iscsid/pdu.c new file mode 100644 index 00000000000..86072b1128d --- /dev/null +++ b/usr.sbin/iscsid/pdu.c @@ -0,0 +1,371 @@ +/* $OpenBSD: pdu.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/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <scsi/iscsi.h> + +#include <errno.h> +#include <event.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> + +#include "iscsid.h" +#include "log.h" + +size_t pdu_readbuf_read(struct pdu_readbuf *, void *, size_t); +size_t pdu_readbuf_len(struct pdu_readbuf *); + +#define PDU_MIN(_x, _y) ((_x) < (_y) ? (_x) : (_y)) + +void * +pdu_gethdr(struct pdu *p) +{ + void *hdr; + + if (!(hdr = calloc(1, sizeof(struct iscsi_pdu)))) + return NULL; + if (pdu_addbuf(p, hdr, sizeof(struct iscsi_pdu), PDU_HEADER)) { + free(hdr); + return NULL; + } + return hdr; +} + +int +text_to_pdu(struct kvp *k, struct pdu *p) +{ + char *buf, *s; + size_t len = 0, rem; + int n, nk; + + nk = 0; + while(k[nk].key) { + len += 2 + strlen(k[nk].key) + strlen(k[nk].value); + nk++; + } + + if (!(buf = pdu_alloc(len))) + return -1; + s = buf; + rem = len; + nk = 0; + while(k[nk].key) { + n = snprintf(s, rem, "%s=%s", k[nk].key, k[nk].value); + if (n == -1 || (size_t)n >= rem) + fatalx("text_to_pdu"); + rem -= n + 1; + s += n + 1; + nk++; + } + + if (pdu_addbuf(p, buf, len, PDU_DATA)) + return -1; + return len; +} + +struct kvp * +pdu_to_text(char *buf, size_t len) +{ + struct kvp *k; + size_t n; + char *eq; + unsigned int nkvp = 0, i; + + if (buf[len - 1]) { + log_debug("pdu_to_text: badly terminated text data"); + return NULL; + } + for(n = 0; n < len; n++) + if (buf[n] == '\0') + nkvp++; + + if (!(k = calloc(nkvp + 1, sizeof(*k)))) + return NULL; + + for (i = 0; i < nkvp; i++) { + eq = strchr(buf, '='); + if (!eq) { + log_debug("pdu_to_text: badly encoded text data"); + free(k); + return NULL; + } + *eq++ = '\0'; + k[i].key = buf; + k[i].value = eq; + buf = eq + strlen(eq) + 1; + } + return k; +} + +/* + * Internal functions to send/recv pdus. + */ + +void +pdu_free_queue(struct pduq *channel) +{ + struct pdu *p; + + while ((p = TAILQ_FIRST(channel))) { + TAILQ_REMOVE(channel, p, entry); + pdu_free(p); + } +} + +ssize_t +pdu_read(struct connection *c) +{ + struct iovec iov[2]; + unsigned int niov = 1; + ssize_t n; + + bzero(&iov, sizeof(iov)); + iov[0].iov_base = c->prbuf.buf + c->prbuf.wpos; + if (c->prbuf.wpos < c->prbuf.rpos) + iov[0].iov_len = c->prbuf.rpos - c->prbuf.wpos; + else { + iov[0].iov_len = c->prbuf.size - c->prbuf.wpos; + if (c->prbuf.rpos > 0) { + niov++; + iov[1].iov_base = c->prbuf.buf; + iov[1].iov_len = c->prbuf.rpos - 1; + } + } + + if ((n = readv(c->fd, iov, niov)) == -1) { + if (errno == EAGAIN || errno == ENOBUFS || + errno == EINTR) /* try later */ + return 0; + else { + log_warn("pdu_read"); + return -1; + } + } + if (n == 0) + /* XXX what should we do on close with remaining data? */ + return 0; + + c->prbuf.wpos += n; + if (c->prbuf.wpos >= c->prbuf.size) + c->prbuf.wpos -= c->prbuf.size; + + return (n); +} + +ssize_t +pdu_write(struct connection *c) +{ + struct iovec iov[PDU_WRIOV]; + struct pdu *b, *nb; + unsigned int niov = 0, j; + size_t off, resid, size; + ssize_t n; + + TAILQ_FOREACH(b, &c->pdu_w, entry) { + if (niov >= PDU_WRIOV) + break; + off = b->resid; + for (j = 0; j < PDU_MAXIOV && niov < PDU_WRIOV; j++) { + if (!b->iov[j].iov_len) + continue; + if (off >= b->iov[j].iov_len) { + off -= b->iov[j].iov_len; + continue; + } + iov[niov].iov_base = (char *)b->iov[j].iov_base + off; + iov[niov++].iov_len = b->iov[j].iov_len - off; + off = 0; + } + } + + if ((n = writev(c->fd, iov, niov)) == -1) { + if (errno == EAGAIN || errno == ENOBUFS || + errno == EINTR) /* try later */ + return 0; + else { + log_warn("pdu_write"); + return -1; + } + } + if (n == 0) + return 0; + + size = n; + for (b = TAILQ_FIRST(&c->pdu_w); b != NULL && size > 0; b = nb) { + nb = TAILQ_NEXT(b, entry); + resid = b->resid; + for (j = 0; j < PDU_MAXIOV; j++) { + if (resid >= b->iov[j].iov_len) + resid -= b->iov[j].iov_len; + else if (size >= b->iov[j].iov_len - resid) { + size -= b->iov[j].iov_len - resid; + b->resid += b->iov[j].iov_len - resid; + resid = 0; + } else { + b->resid += size; + size = 0; + break; + } + } + if (j == PDU_MAXIOV) { + /* all written */ + TAILQ_REMOVE(&c->pdu_w, b, entry); + pdu_free(b); + } + } + return n; +} + +int +pdu_pending(struct connection *c) +{ + if (TAILQ_EMPTY(&c->pdu_w)) + return 0; + else + return 1; +} + +void +pdu_parse(struct connection *c) +{ + struct pdu *p; + struct iscsi_pdu *ipdu; + char *ahb, *db; + size_t ahslen, dlen, off; + ssize_t n; + unsigned int j; + +/* XXX XXX I DON'T LIKE YOU. CAN I REWRITE YOU? */ + + do { + if (!(p = c->prbuf.wip)) { + /* get and parse base header */ + if (pdu_readbuf_len(&c->prbuf) < sizeof(*ipdu)) + return; + if (!(p = pdu_new())) + goto fail; + if (!(ipdu = pdu_gethdr(p))) + goto fail; + + c->prbuf.wip = p; + /* + * XXX maybe a pdu_readbuf_peek() would allow a better + * error handling. + */ + pdu_readbuf_read(&c->prbuf, ipdu, sizeof(*ipdu)); + + ahslen = ipdu->ahslen * sizeof(u_int32_t); + if (ahslen != 0) { + if (!(ahb = pdu_alloc(ahslen)) || + pdu_addbuf(p, ahb, ahslen, PDU_AHS)) + goto fail; + } + + dlen = ipdu->datalen[0] << 16 | ipdu->datalen[1] << 8 | + ipdu->datalen[2]; + if (dlen != 0) { + if (!(db = pdu_alloc(dlen)) || + pdu_addbuf(p, db, dlen, PDU_DATA)) + goto fail; + } + + p->resid = sizeof(*ipdu); + } else { + off = p->resid; + for (j = 0; j < PDU_MAXIOV; j++) { + if (off >= p->iov[j].iov_len) + off -= p->iov[j].iov_len; + else { + n = pdu_readbuf_read(&c->prbuf, + (char *)p->iov[j].iov_base + off, + p->iov[j].iov_len - off); + p->resid += n; + if (n == 0 || off + n != + p->iov[j].iov_len) + return; + } + } + task_pdu_cb(c, p); + c->prbuf.wip = NULL; + } + } while (1); +fail: + fatalx("pdu_parse hit a space oddity"); +} + +size_t +pdu_readbuf_read(struct pdu_readbuf *rb, void *ptr, size_t len) +{ + size_t l; + + if (rb->rpos == rb->wpos) { + return (0); + } else if (rb->rpos < rb->wpos) { + l = PDU_MIN(rb->wpos - rb->rpos, len); + bcopy(rb->buf + rb->rpos, ptr, l); + rb->rpos += l; + return l; + } else { + l = PDU_MIN(rb->size - rb->rpos, len); + bcopy(rb->buf + rb->rpos, ptr, l); + rb->rpos += l; + if (rb->rpos == rb->size) + rb->rpos = 0; + if (l < len) + return l + pdu_readbuf_read(rb, (char *)ptr + l, + len - l); + return l; + } +} + +size_t +pdu_readbuf_len(struct pdu_readbuf *rb) +{ + if (rb->rpos <= rb->wpos) + return rb->wpos - rb->rpos; + else + return rb->size - (rb->rpos - rb->wpos); +} + +int +pdu_readbuf_set(struct pdu_readbuf *rb, size_t bsize) +{ + char *nb; + + if (bsize < rb->size) + /* can't shrink */ + return 0; + if ((nb = realloc(rb->buf, bsize)) == NULL) { + free(rb->buf); + return -1; + } + rb->buf = nb; + rb->size = bsize; + return 0; +} + +void +pdu_readbuf_free(struct pdu_readbuf *rb) +{ + free(rb->buf); +} diff --git a/usr.sbin/iscsid/task.c b/usr.sbin/iscsid/task.c new file mode 100644 index 00000000000..70a4c85534c --- /dev/null +++ b/usr.sbin/iscsid/task.c @@ -0,0 +1,124 @@ +/* $OpenBSD: task.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/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <scsi/iscsi.h> + +#include <errno.h> +#include <event.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> + +#include "iscsid.h" +#include "log.h" + +/* + * Task handling, PDU are attached to tasks and task are scheduled accross + * all connections of a session. + */ + +void +task_init(struct task *t, struct session *s, int immediate, void *carg, + void (*c)(struct connection *, void *, struct pdu *)) +{ + TAILQ_INIT(&t->sendq); + TAILQ_INIT(&t->recvq); + t->callback = c; + t->callarg = carg; + t->itt = s->itt++; /* XXX we could do better here */ + t->cmdseqnum = s->cmdseqnum; + if (!immediate) + s->cmdseqnum++; +} + +void +task_cleanup(struct task *t, struct connection *c) +{ +/* XXX THIS FEELS WRONG FOR NOW */ + pdu_free_queue(&t->sendq); + pdu_free_queue(&t->recvq); + /* XXX need some state to know if queued or not */ + TAILQ_REMOVE(&c->tasks, t, entry); +} + +void +task_pdu_add(struct task *t, struct pdu *p) +{ + struct iscsi_pdu *ipdu; + + /* fixup the pdu by setting the itt and seqnum if needed */ + ipdu = pdu_getbuf(p, NULL, PDU_HEADER); + ipdu->itt = ntohl(t->itt); + 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_LOGOUT_REQUEST: + ipdu->cmdsn = ntohl(t->cmdseqnum); + break; + } + + TAILQ_INSERT_TAIL(&t->sendq, p, entry); +} + +void +task_pdu_cb(struct connection *c, struct pdu *p) +{ + struct task *t; + struct iscsi_pdu *ipdu; + u_int32_t itt; + + ipdu = pdu_getbuf(p, NULL, PDU_HEADER); + switch (ISCSI_PDU_OPCODE(ipdu->opcode)) { + case ISCSI_OP_T_NOP: + case ISCSI_OP_LOGIN_RESPONSE: + case ISCSI_OP_TEXT_RESPONSE: + case ISCSI_OP_LOGOUT_RESPONSE: + case ISCSI_OP_SCSI_RESPONSE: + case ISCSI_OP_R2T: + case ISCSI_OP_DATA_IN: + itt = ntohl(ipdu->itt); + c->expstatsn = ntohl(ipdu->cmdsn) + 1; + + /* XXX for now search the task on the connection queue + later on this should be moved to a per session RB tree but + now I do the quick ugly thing. */ + TAILQ_FOREACH(t, &c->tasks, entry) { + if (itt == t->itt) + break; + } + if (t) + t->callback(c, t->callarg, p); + else { + log_debug("no task for PDU found"); + pdu_free(p); + } + break; + default: +log_pdu(p, 1); + log_warnx("not handled yet. fix me"); + pdu_free(p); + } +} diff --git a/usr.sbin/iscsid/util.c b/usr.sbin/iscsid/util.c new file mode 100644 index 00000000000..8e1242efbd4 --- /dev/null +++ b/usr.sbin/iscsid/util.c @@ -0,0 +1,143 @@ +/* $OpenBSD: util.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/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <scsi/iscsi.h> + +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> + +#include "iscsid.h" +#include "log.h" + +struct pdu * +pdu_new(void) +{ + struct pdu *p; + + if (!(p = calloc(1, sizeof(*p)))) + return NULL; + return p; +} + +void * +pdu_alloc(size_t len) +{ + return malloc(PDU_LEN(len)); +} + +void * +pdu_dup(void *data, size_t len) +{ + void *p; + + if ((p = malloc(PDU_LEN(len)))) + bcopy(data, p, len); + return p; +} + +int +pdu_addbuf(struct pdu *p, void *buf, size_t len, unsigned int elm) +{ + if (len & 0x3) { + bzero((char *)buf + len, 4 - (len & 0x3)); + len += 4 - (len & 0x3); + } + + if (elm < PDU_MAXIOV) + if (!p->iov[elm].iov_base) { + p->iov[elm].iov_base = buf; + p->iov[elm].iov_len = len; + return 0; + } + + /* no space left */ + return -1; +} + +void * +pdu_getbuf(struct pdu *p, size_t *len, unsigned int elm) +{ + if (len) + *len = 0; + if (elm < PDU_MAXIOV) + if (p->iov[elm].iov_base) { + if (len) + *len = p->iov[elm].iov_len; + return p->iov[elm].iov_base; + } + + return NULL; +} + +void +pdu_free(struct pdu *p) +{ + unsigned int j; + + for (j = 0; j < PDU_MAXIOV; j++) + free(p->iov[j].iov_base); + free(p); +} + +int +socket_setblockmode(int fd, int nonblocking) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) == -1) + return -1; + + if (nonblocking) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if ((flags = fcntl(fd, F_SETFL, flags)) == -1) + return -1; + return 0; +} + +const char * +log_sockaddr(void *arg) +{ + struct sockaddr *sa = arg; + char port[6]; + char host[NI_MAXHOST]; + static char buf[NI_MAXHOST + 8]; + + if (getnameinfo(sa, sa->sa_len, host, sizeof(host), port, sizeof(port), + NI_NUMERICHOST)) + return "unknown"; + if (port[0] == '0') + strlcpy(buf, host, sizeof(buf)); + else if (sa->sa_family == AF_INET) + snprintf(buf, sizeof(buf), "%s:%s", host, port); + else + snprintf(buf, sizeof(buf), "[%s]:%s", host, port); + return buf; +} diff --git a/usr.sbin/iscsid/vscsi.c b/usr.sbin/iscsid/vscsi.c new file mode 100644 index 00000000000..1517ff397bb --- /dev/null +++ b/usr.sbin/iscsid/vscsi.c @@ -0,0 +1,286 @@ +/* $OpenBSD: vscsi.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/types.h> +#include <sys/ioctl.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <scsi/iscsi.h> +#include <scsi/scsi_all.h> +#include <dev/vscsivar.h> + +#include <event.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> + +#include "iscsid.h" +#include "log.h" + +struct vscsi { + struct event ev; + int fd; +} v; + +struct scsi_task { + struct task task; + int tag; + u_int target; + u_int lun; + size_t datalen; +}; + +void vscsi_callback(struct connection *, void *, struct pdu *); +void vscsi_dataout(struct session *, struct scsi_task *, u_int32_t, size_t); + +void +vscsi_open(char *dev) +{ + if ((v.fd = open(dev, O_RDWR)) == -1) + fatal("vscsi_open"); + + event_set(&v.ev, v.fd, EV_READ|EV_PERSIST, vscsi_dispatch, NULL); + event_add(&v.ev, NULL); +} + +void +vscsi_dispatch(int fd, short event, void *arg) +{ + struct vscsi_ioc_i2t i2t; + struct iscsi_pdu_scsi_request *sreq; + struct session *s; + struct scsi_task *t; + struct pdu *p; +#if 0 + char *buf; + u_int32_t t32; +#endif + + if (!(event & EV_READ)) { + log_debug("spurious read call"); + return; + } + + if (ioctl(v.fd, VSCSI_I2T, &i2t) == -1) + fatal("vscsi_dispatch"); + + s = initiator_t2s(i2t.target); + if (s == NULL) + fatalx("vscsi_dispatch: unknown target"); + + if (!(t = calloc(1, sizeof(*t)))) + fatal("vscsi_dispatch"); + + t->tag = i2t.tag; + t->target = i2t.target; + t->lun = i2t.lun; + + if (!(p = pdu_new())) + fatal("vscsi_dispatch"); + if (!(sreq = pdu_gethdr(p))) + fatal("vscsi_dispatch"); + + sreq->opcode = ISCSI_OP_SCSI_REQUEST; + /* XXX use untagged commands, dlg sais so */ + sreq->flags = ISCSI_SCSI_F_F | ISCSI_SCSI_ATTR_UNTAGGED; + if (i2t.direction == VSCSI_DIR_WRITE) + sreq->flags |= ISCSI_SCSI_F_W; + if (i2t.direction == VSCSI_DIR_READ) + sreq->flags |= ISCSI_SCSI_F_R; + sreq->bytes = htonl(i2t.datalen); + + /* XXX LUN HANDLING !!!! */ + + bcopy(&i2t.cmd, sreq->cdb, i2t.cmdlen); + +#if 0 + if (i2t.direction == VSCSI_DIR_WRITE) { + if (!(buf = pdu_alloc(i2t.datalen))) + fatal("vscsi_dispatch"); + t32 = htonl(i2t.datalen); + bcopy(&t32, &sreq->ahslen, sizeof(t32)); + vscsi_data(VSCSI_DATA_WRITE, i2t.tag, buf, i2t.datalen); + pdu_addbuf(p, buf, i2t.datalen, PDU_DATA); + } +#endif + + task_init(&t->task, s, 0, t, vscsi_callback); + task_pdu_add(&t->task, p); + session_task_issue(s, &t->task); +} + +void +vscsi_data(unsigned long req, int tag, void *buf, size_t len) +{ + struct vscsi_ioc_data data; + + data.tag = tag; + data.data = buf; + data.datalen = len; + + if (ioctl(v.fd, req, &data) == -1) + fatal("vscsi_data"); +} + +void +vscsi_status(int tag, int status, void *buf, size_t len) +{ + struct vscsi_ioc_t2i t2i; + + bzero(&t2i, sizeof(t2i)); + t2i.tag = tag; + t2i.status = status; + if (buf) { + if (len > sizeof(t2i.sense)) + fatal("vscsi_status: I'm sorry, Dave. " + "I'm afraid I can't do that."); + bcopy(buf, &t2i.sense, len); + } + + if (ioctl(v.fd, VSCSI_T2I, &t2i) == -1) + fatal("vscsi_status"); +} + +void +vscsi_event(unsigned long req, u_int target, u_int lun) +{ + struct vscsi_ioc_devevent devev; + + devev.target = target; + devev.lun = lun; + + if (ioctl(v.fd, req, &devev) == -1) + fatal("vscsi_event"); +} + +void +vscsi_callback(struct connection *c, void *arg, struct pdu *p) +{ + struct scsi_task *t = arg; + struct iscsi_pdu_scsi_response *sresp; + struct iscsi_pdu_rt2 *r2t; + int status = VSCSI_STAT_DONE; + u_char *buf = NULL; + size_t size = 0, n; + int tag; + + sresp = pdu_getbuf(p, NULL, PDU_HEADER); + switch (ISCSI_PDU_OPCODE(sresp->opcode)) { + case ISCSI_OP_SCSI_RESPONSE: + task_cleanup(&t->task, c); + tag = t->tag; + free(t); + + if (!(sresp->flags & 0x80) || (sresp->flags & 0x06) == 0x06 || + (sresp->flags & 0x18) == 0x18) { + log_debug("vscsi_callback: bad scsi response"); + conn_fail(c); + break; + } + /* XXX handle the various serial numbers */ + if (sresp->response) { + status = VSCSI_STAT_ERR; + goto send_status; + } + switch (sresp->status) { + case ISCSI_SCSI_STAT_GOOD: + break; + case ISCSI_SCSI_STAT_CHCK_COND: + status = VSCSI_STAT_SENSE; + /* stupid encoding of sense data in the data segment */ + buf = pdu_getbuf(p, &n, PDU_DATA); + if (buf) { + size = buf[0] << 8 | buf[1]; + buf += 2; + } + break; + default: + status = VSCSI_STAT_ERR; + break; + } +send_status: + vscsi_status(tag, status, buf, size); + break; + case ISCSI_OP_DATA_IN: + buf = pdu_getbuf(p, &n, PDU_DATA); + size = sresp->datalen[0] << 16 | sresp->datalen[1] << 8 | + sresp->datalen[2]; + if (size > n) + fatal("This does not work as it should"); + vscsi_data(VSCSI_DATA_READ, t->tag, buf, size); + if (sresp->flags & 1) { + log_debug("and a vscsi_status"); + task_cleanup(&t->task, c); + vscsi_status(t->tag, status, NULL, 0); + free(t); + } + break; + case ISCSI_OP_R2T: + task_cleanup(&t->task, c); + r2t = (struct iscsi_pdu_rt2 *)sresp; + if (ntohl(r2t->buffer_offs)) + fatalx("vscsi: r2t bummer failure"); + size = ntohl(r2t->desired_datalen); + + vscsi_dataout(c->session, t, r2t->ttt, size); + break; + default: + log_debug("scsi task: tag %d, target %d lun %d", t->tag, + t->target, t->lun); + log_pdu(p, 1); + } + pdu_free(p); +} + +void +vscsi_dataout(struct session *s, struct scsi_task *t, u_int32_t ttt, size_t len) +{ + struct pdu *p; + struct iscsi_pdu_data_out *dout; + u_char *buf = NULL; + size_t size, off; + u_int32_t t32, dsn = 0; + + for (off = 0; off < len; off += size) { + size = len - off > 8 * 1024 ? 8 * 1024 : len - off; + + if (!(p = pdu_new())) + fatal("vscsi_r2t"); + if (!(dout = pdu_gethdr(p))) + fatal("vscsi_r2t"); + + dout->opcode = ISCSI_OP_DATA_OUT; + if (off + size == len) + dout->flags = 0x80; /* XXX magic value: F flag*/ + dout->ttt = ttt; + dout->datasn = htonl(dsn++); + t32 = htonl(size); + bcopy(&t32, &dout->ahslen, sizeof(t32)); + + dout->buffer_offs = htonl(off); + if (!(buf = pdu_alloc(size))) + fatal("vscsi_r2t"); + + vscsi_data(VSCSI_DATA_WRITE, t->tag, buf, size); + pdu_addbuf(p, buf, size, PDU_DATA); + task_pdu_add(&t->task, p); + } + session_task_issue(s, &t->task); +} |