diff options
author | 2010-09-24 09:43:19 +0000 | |
---|---|---|
committer | 2010-09-24 09:43:19 +0000 | |
commit | bde1ae237b27fe974d1439632ef2c60652523f9c (patch) | |
tree | f82ac3859d73a643088d58b7ace6cfae900e1d30 | |
parent | for rdr-to and nat-to, mention in which direction they are usually used (diff) | |
download | wireguard-openbsd-bde1ae237b27fe974d1439632ef2c60652523f9c.tar.xz wireguard-openbsd-bde1ae237b27fe974d1439632ef2c60652523f9c.zip |
iSCSI Initiatior daemon using vscsi(4).
Currently implements the absolute minimum of the protocol to make
it work against targets. Many things still in flux but we're annoyed
to work outside of the tree. Commited from a source tree on an iSCSI
disk served via iscsid but it is not yet production ready.
OK dlg@, matthew@, deraadt@
-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); +} |