diff options
Diffstat (limited to 'src/spdk/lib/iscsi')
-rw-r--r-- | src/spdk/lib/iscsi/Makefile | 50 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/conn.c | 1714 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/conn.h | 237 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/init_grp.c | 787 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/init_grp.h | 81 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/iscsi.c | 4797 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/iscsi.h | 465 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/iscsi_rpc.c | 1639 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/iscsi_subsystem.c | 1577 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/md5.c | 75 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/md5.h | 52 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/param.c | 1216 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/param.h | 94 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/portal_grp.c | 655 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/portal_grp.h | 90 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/spdk_iscsi.map | 11 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/task.c | 98 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/task.h | 188 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/tgt_node.c | 1607 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/tgt_node.h | 147 |
20 files changed, 15580 insertions, 0 deletions
diff --git a/src/spdk/lib/iscsi/Makefile b/src/spdk/lib/iscsi/Makefile new file mode 100644 index 000000000..2c663d880 --- /dev/null +++ b/src/spdk/lib/iscsi/Makefile @@ -0,0 +1,50 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +SO_VER := 3 +SO_MINOR := 0 + +CFLAGS += -I$(SPDK_ROOT_DIR)/lib +C_SRCS = conn.c \ + init_grp.c iscsi.c md5.c param.c portal_grp.c \ + tgt_node.c iscsi_subsystem.c \ + iscsi_rpc.c task.c +LIBNAME = iscsi +LOCAL_SYS_LIBS = -lcrypto + +SPDK_MAP_FILE = $(abspath $(CURDIR)/spdk_iscsi.map) + +include $(SPDK_ROOT_DIR)/mk/spdk.lib.mk diff --git a/src/spdk/lib/iscsi/conn.c b/src/spdk/lib/iscsi/conn.c new file mode 100644 index 000000000..4c7a54fcf --- /dev/null +++ b/src/spdk/lib/iscsi/conn.c @@ -0,0 +1,1714 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include "spdk/endian.h" +#include "spdk/env.h" +#include "spdk/event.h" +#include "spdk/likely.h" +#include "spdk/thread.h" +#include "spdk/queue.h" +#include "spdk/trace.h" +#include "spdk/net.h" +#include "spdk/sock.h" +#include "spdk/string.h" + +#include "spdk_internal/log.h" + +#include "iscsi/task.h" +#include "iscsi/conn.h" +#include "iscsi/tgt_node.h" +#include "iscsi/portal_grp.h" + +#define MAKE_DIGEST_WORD(BUF, CRC32C) \ + ( ((*((uint8_t *)(BUF)+0)) = (uint8_t)((uint32_t)(CRC32C) >> 0)), \ + ((*((uint8_t *)(BUF)+1)) = (uint8_t)((uint32_t)(CRC32C) >> 8)), \ + ((*((uint8_t *)(BUF)+2)) = (uint8_t)((uint32_t)(CRC32C) >> 16)), \ + ((*((uint8_t *)(BUF)+3)) = (uint8_t)((uint32_t)(CRC32C) >> 24))) + +#define SPDK_ISCSI_CONNECTION_MEMSET(conn) \ + memset(&(conn)->portal, 0, sizeof(*(conn)) - \ + offsetof(struct spdk_iscsi_conn, portal)); + +struct spdk_iscsi_conn *g_conns_array = MAP_FAILED; +static int g_conns_array_fd = -1; +static char g_shm_name[64]; + +static TAILQ_HEAD(, spdk_iscsi_conn) g_free_conns = TAILQ_HEAD_INITIALIZER(g_free_conns); +static TAILQ_HEAD(, spdk_iscsi_conn) g_active_conns = TAILQ_HEAD_INITIALIZER(g_active_conns); + +static pthread_mutex_t g_conns_mutex = PTHREAD_MUTEX_INITIALIZER; + +static struct spdk_poller *g_shutdown_timer = NULL; + +static void iscsi_conn_sock_cb(void *arg, struct spdk_sock_group *group, + struct spdk_sock *sock); + +static struct spdk_iscsi_conn * +allocate_conn(void) +{ + struct spdk_iscsi_conn *conn; + + pthread_mutex_lock(&g_conns_mutex); + conn = TAILQ_FIRST(&g_free_conns); + if (conn != NULL) { + assert(!conn->is_valid); + TAILQ_REMOVE(&g_free_conns, conn, conn_link); + SPDK_ISCSI_CONNECTION_MEMSET(conn); + conn->is_valid = 1; + + TAILQ_INSERT_TAIL(&g_active_conns, conn, conn_link); + } + pthread_mutex_unlock(&g_conns_mutex); + + return conn; +} + +static void +_free_conn(struct spdk_iscsi_conn *conn) +{ + TAILQ_REMOVE(&g_active_conns, conn, conn_link); + + memset(conn->portal_host, 0, sizeof(conn->portal_host)); + memset(conn->portal_port, 0, sizeof(conn->portal_port)); + conn->is_valid = 0; + + TAILQ_INSERT_TAIL(&g_free_conns, conn, conn_link); +} + +static void +free_conn(struct spdk_iscsi_conn *conn) +{ + pthread_mutex_lock(&g_conns_mutex); + _free_conn(conn); + pthread_mutex_unlock(&g_conns_mutex); +} + +static void +_iscsi_conns_cleanup(void) +{ + if (g_conns_array != MAP_FAILED) { + munmap(g_conns_array, sizeof(struct spdk_iscsi_conn) * + MAX_ISCSI_CONNECTIONS); + g_conns_array = MAP_FAILED; + } + + if (g_conns_array_fd >= 0) { + close(g_conns_array_fd); + g_conns_array_fd = -1; + shm_unlink(g_shm_name); + } +} + +int initialize_iscsi_conns(void) +{ + size_t conns_size = sizeof(struct spdk_iscsi_conn) * MAX_ISCSI_CONNECTIONS; + uint32_t i; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_iscsi_init\n"); + + snprintf(g_shm_name, sizeof(g_shm_name), "/spdk_iscsi_conns.%d", spdk_app_get_shm_id()); + g_conns_array_fd = shm_open(g_shm_name, O_RDWR | O_CREAT, 0600); + if (g_conns_array_fd < 0) { + SPDK_ERRLOG("could not shm_open %s\n", g_shm_name); + goto err; + } + + if (ftruncate(g_conns_array_fd, conns_size) != 0) { + SPDK_ERRLOG("could not ftruncate\n"); + goto err; + } + g_conns_array = mmap(0, conns_size, PROT_READ | PROT_WRITE, MAP_SHARED, + g_conns_array_fd, 0); + + if (g_conns_array == MAP_FAILED) { + SPDK_ERRLOG("could not mmap cons array file %s (%d)\n", g_shm_name, errno); + goto err; + } + + memset(g_conns_array, 0, conns_size); + + for (i = 0; i < MAX_ISCSI_CONNECTIONS; i++) { + g_conns_array[i].id = i; + TAILQ_INSERT_TAIL(&g_free_conns, &g_conns_array[i], conn_link); + } + + return 0; + +err: + _iscsi_conns_cleanup(); + + return -1; +} + +static void +iscsi_poll_group_add_conn(struct spdk_iscsi_poll_group *pg, struct spdk_iscsi_conn *conn) +{ + int rc; + + rc = spdk_sock_group_add_sock(pg->sock_group, conn->sock, iscsi_conn_sock_cb, conn); + if (rc < 0) { + SPDK_ERRLOG("Failed to add sock=%p of conn=%p\n", conn->sock, conn); + return; + } + + conn->is_stopped = false; + STAILQ_INSERT_TAIL(&pg->connections, conn, pg_link); +} + +static void +iscsi_poll_group_remove_conn(struct spdk_iscsi_poll_group *pg, struct spdk_iscsi_conn *conn) +{ + int rc; + + assert(conn->sock != NULL); + rc = spdk_sock_group_remove_sock(pg->sock_group, conn->sock); + if (rc < 0) { + SPDK_ERRLOG("Failed to remove sock=%p of conn=%p\n", conn->sock, conn); + } + + conn->is_stopped = true; + STAILQ_REMOVE(&pg->connections, conn, spdk_iscsi_conn, pg_link); +} + +static void +iscsi_conn_start(void *ctx) +{ + struct spdk_iscsi_conn *conn = ctx; + + iscsi_poll_group_add_conn(conn->pg, conn); +} + +int +iscsi_conn_construct(struct spdk_iscsi_portal *portal, + struct spdk_sock *sock) +{ + struct spdk_iscsi_poll_group *pg; + struct spdk_iscsi_conn *conn; + int i, rc; + + conn = allocate_conn(); + if (conn == NULL) { + SPDK_ERRLOG("Could not allocate connection.\n"); + return -1; + } + + pthread_mutex_lock(&g_iscsi.mutex); + conn->timeout = g_iscsi.timeout * spdk_get_ticks_hz(); /* seconds to TSC */ + conn->nopininterval = g_iscsi.nopininterval; + conn->nopininterval *= spdk_get_ticks_hz(); /* seconds to TSC */ + conn->nop_outstanding = false; + conn->data_out_cnt = 0; + conn->data_in_cnt = 0; + conn->disable_chap = portal->group->disable_chap; + conn->require_chap = portal->group->require_chap; + conn->mutual_chap = portal->group->mutual_chap; + conn->chap_group = portal->group->chap_group; + pthread_mutex_unlock(&g_iscsi.mutex); + conn->MaxRecvDataSegmentLength = 8192; /* RFC3720(12.12) */ + + conn->portal = portal; + conn->pg_tag = portal->group->tag; + memcpy(conn->portal_host, portal->host, strlen(portal->host)); + memcpy(conn->portal_port, portal->port, strlen(portal->port)); + conn->sock = sock; + + conn->state = ISCSI_CONN_STATE_INVALID; + conn->login_phase = ISCSI_SECURITY_NEGOTIATION_PHASE; + conn->ttt = 0; + + conn->partial_text_parameter = NULL; + + for (i = 0; i < MAX_CONNECTION_PARAMS; i++) { + conn->conn_param_state_negotiated[i] = false; + } + + for (i = 0; i < MAX_SESSION_PARAMS; i++) { + conn->sess_param_state_negotiated[i] = false; + } + + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_AWAIT_PDU_READY; + + TAILQ_INIT(&conn->write_pdu_list); + TAILQ_INIT(&conn->snack_pdu_list); + TAILQ_INIT(&conn->queued_r2t_tasks); + TAILQ_INIT(&conn->active_r2t_tasks); + TAILQ_INIT(&conn->queued_datain_tasks); + memset(&conn->luns, 0, sizeof(conn->luns)); + + rc = spdk_sock_getaddr(sock, conn->target_addr, sizeof conn->target_addr, NULL, + conn->initiator_addr, sizeof conn->initiator_addr, NULL); + if (rc < 0) { + SPDK_ERRLOG("spdk_sock_getaddr() failed\n"); + goto error_return; + } + + /* set low water mark */ + rc = spdk_sock_set_recvlowat(conn->sock, 1); + if (rc != 0) { + SPDK_ERRLOG("spdk_sock_set_recvlowat() failed\n"); + goto error_return; + } + + /* set default params */ + rc = iscsi_conn_params_init(&conn->params); + if (rc < 0) { + SPDK_ERRLOG("iscsi_conn_params_init() failed\n"); + goto error_return; + } + conn->logout_request_timer = NULL; + conn->logout_timer = NULL; + conn->shutdown_timer = NULL; + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Launching connection on acceptor thread\n"); + conn->pending_task_cnt = 0; + + /* Get the first poll group. */ + pg = TAILQ_FIRST(&g_iscsi.poll_group_head); + if (pg == NULL) { + SPDK_ERRLOG("There is no poll group.\n"); + assert(false); + goto error_return; + } + + conn->pg = pg; + spdk_thread_send_msg(spdk_io_channel_get_thread(spdk_io_channel_from_ctx(pg)), + iscsi_conn_start, conn); + return 0; + +error_return: + iscsi_param_free(conn->params); + free_conn(conn); + return -1; +} + +void +iscsi_conn_free_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + iscsi_conn_xfer_complete_cb cb_fn; + void *cb_arg; + + cb_fn = pdu->cb_fn; + cb_arg = pdu->cb_arg; + + assert(cb_fn != NULL); + pdu->cb_fn = NULL; + + if (pdu->task) { + iscsi_task_put(pdu->task); + } + iscsi_put_pdu(pdu); + + cb_fn(cb_arg); +} + +static int +iscsi_conn_free_tasks(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_pdu *pdu, *tmp_pdu; + struct spdk_iscsi_task *iscsi_task, *tmp_iscsi_task; + + TAILQ_FOREACH_SAFE(pdu, &conn->snack_pdu_list, tailq, tmp_pdu) { + TAILQ_REMOVE(&conn->snack_pdu_list, pdu, tailq); + iscsi_conn_free_pdu(conn, pdu); + } + + TAILQ_FOREACH_SAFE(iscsi_task, &conn->queued_datain_tasks, link, tmp_iscsi_task) { + if (!iscsi_task->is_queued) { + TAILQ_REMOVE(&conn->queued_datain_tasks, iscsi_task, link); + iscsi_task_put(iscsi_task); + } + } + + /* We have to parse conn->write_pdu_list in the end. In iscsi_conn_free_pdu(), + * iscsi_conn_handle_queued_datain_tasks() may be called, and + * iscsi_conn_handle_queued_datain_tasks() will parse conn->queued_datain_tasks + * and may stack some PDUs to conn->write_pdu_list. Hence when we come here, we + * have to ensure there is no associated task in conn->queued_datain_tasks. + */ + TAILQ_FOREACH_SAFE(pdu, &conn->write_pdu_list, tailq, tmp_pdu) { + TAILQ_REMOVE(&conn->write_pdu_list, pdu, tailq); + iscsi_conn_free_pdu(conn, pdu); + } + + if (conn->pending_task_cnt) { + return -1; + } + + return 0; +} + +static void +iscsi_conn_cleanup_backend(struct spdk_iscsi_conn *conn) +{ + int rc; + struct spdk_iscsi_tgt_node *target; + + if (conn->sess->connections > 1) { + /* connection specific cleanup */ + } else if (!g_iscsi.AllowDuplicateIsid) { + /* clean up all tasks to all LUNs for session */ + target = conn->sess->target; + if (target != NULL) { + rc = iscsi_tgt_node_cleanup_luns(conn, target); + if (rc < 0) { + SPDK_ERRLOG("target abort failed\n"); + } + } + } +} + +static void +iscsi_conn_free(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_sess *sess; + int idx; + uint32_t i; + + pthread_mutex_lock(&g_conns_mutex); + + if (conn->sess == NULL) { + goto end; + } + + idx = -1; + sess = conn->sess; + conn->sess = NULL; + + for (i = 0; i < sess->connections; i++) { + if (sess->conns[i] == conn) { + idx = i; + break; + } + } + + if (idx < 0) { + SPDK_ERRLOG("remove conn not found\n"); + } else { + for (i = idx; i < sess->connections - 1; i++) { + sess->conns[i] = sess->conns[i + 1]; + } + sess->conns[sess->connections - 1] = NULL; + sess->connections--; + + if (sess->connections == 0) { + /* cleanup last connection */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "cleanup last conn free sess\n"); + iscsi_free_sess(sess); + } + } + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Terminating connections(tsih %d): %d\n", + sess->tsih, sess->connections); + +end: + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "cleanup free conn\n"); + iscsi_param_free(conn->params); + _free_conn(conn); + + pthread_mutex_unlock(&g_conns_mutex); +} + +static void +iscsi_conn_close_lun(struct spdk_iscsi_conn *conn, int lun_id) +{ + struct spdk_iscsi_lun *iscsi_lun; + + iscsi_lun = conn->luns[lun_id]; + if (iscsi_lun == NULL) { + return; + } + + spdk_scsi_lun_free_io_channel(iscsi_lun->desc); + spdk_scsi_lun_close(iscsi_lun->desc); + spdk_poller_unregister(&iscsi_lun->remove_poller); + free(iscsi_lun); + + conn->luns[lun_id] = NULL; +} + +static void +iscsi_conn_close_luns(struct spdk_iscsi_conn *conn) +{ + int i; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + iscsi_conn_close_lun(conn, i); + } +} + +static bool +iscsi_conn_check_tasks_for_lun(struct spdk_iscsi_conn *conn, + struct spdk_scsi_lun *lun) +{ + struct spdk_iscsi_pdu *pdu, *tmp_pdu; + struct spdk_iscsi_task *task; + + assert(lun != NULL); + + /* We can remove deferred PDUs safely because they are already flushed. */ + TAILQ_FOREACH_SAFE(pdu, &conn->snack_pdu_list, tailq, tmp_pdu) { + if (lun == pdu->task->scsi.lun) { + TAILQ_REMOVE(&conn->snack_pdu_list, pdu, tailq); + iscsi_conn_free_pdu(conn, pdu); + } + } + + TAILQ_FOREACH(task, &conn->queued_datain_tasks, link) { + if (lun == task->scsi.lun) { + return false; + } + } + + /* This check loop works even when connection exits in the middle of LUN hotplug + * because all PDUs in write_pdu_list are removed in iscsi_conn_free_tasks(). + */ + TAILQ_FOREACH(pdu, &conn->write_pdu_list, tailq) { + if (pdu->task && lun == pdu->task->scsi.lun) { + return false; + } + } + + return true; +} + +static int +iscsi_conn_remove_lun(void *ctx) +{ + struct spdk_iscsi_lun *iscsi_lun = ctx; + struct spdk_iscsi_conn *conn = iscsi_lun->conn; + struct spdk_scsi_lun *lun = iscsi_lun->lun; + int lun_id = spdk_scsi_lun_get_id(lun); + + if (!iscsi_conn_check_tasks_for_lun(conn, lun)) { + return SPDK_POLLER_BUSY; + } + iscsi_conn_close_lun(conn, lun_id); + return SPDK_POLLER_BUSY; +} + +static void +_iscsi_conn_hotremove_lun(void *ctx) +{ + struct spdk_iscsi_lun *iscsi_lun = ctx; + struct spdk_iscsi_conn *conn = iscsi_lun->conn; + struct spdk_scsi_lun *lun = iscsi_lun->lun; + + assert(spdk_io_channel_get_thread(spdk_io_channel_from_ctx(conn->pg)) == + spdk_get_thread()); + + /* If a connection is already in stating status, just return */ + if (conn->state >= ISCSI_CONN_STATE_EXITING) { + return; + } + + iscsi_clear_all_transfer_task(conn, lun, NULL); + + iscsi_lun->remove_poller = SPDK_POLLER_REGISTER(iscsi_conn_remove_lun, iscsi_lun, + 1000); +} + +static void +iscsi_conn_hotremove_lun(struct spdk_scsi_lun *lun, void *remove_ctx) +{ + struct spdk_iscsi_conn *conn = remove_ctx; + int lun_id = spdk_scsi_lun_get_id(lun); + struct spdk_iscsi_lun *iscsi_lun; + + iscsi_lun = conn->luns[lun_id]; + if (iscsi_lun == NULL) { + SPDK_ERRLOG("LUN hotplug was notified to the unallocated LUN %d.\n", lun_id); + return; + } + + spdk_thread_send_msg(spdk_io_channel_get_thread(spdk_io_channel_from_ctx(conn->pg)), + _iscsi_conn_hotremove_lun, iscsi_lun); +} + +static int +iscsi_conn_open_lun(struct spdk_iscsi_conn *conn, int lun_id, + struct spdk_scsi_lun *lun) +{ + int rc; + struct spdk_iscsi_lun *iscsi_lun; + + iscsi_lun = calloc(1, sizeof(*iscsi_lun)); + if (iscsi_lun == NULL) { + return -ENOMEM; + } + + iscsi_lun->conn = conn; + iscsi_lun->lun = lun; + + rc = spdk_scsi_lun_open(lun, iscsi_conn_hotremove_lun, conn, &iscsi_lun->desc); + if (rc != 0) { + free(iscsi_lun); + return rc; + } + + rc = spdk_scsi_lun_allocate_io_channel(iscsi_lun->desc); + if (rc != 0) { + spdk_scsi_lun_close(iscsi_lun->desc); + free(iscsi_lun); + return rc; + } + + conn->luns[lun_id] = iscsi_lun; + + return 0; +} + +static void +iscsi_conn_open_luns(struct spdk_iscsi_conn *conn) +{ + int i, rc; + struct spdk_scsi_lun *lun; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + lun = spdk_scsi_dev_get_lun(conn->dev, i); + if (lun == NULL) { + continue; + } + + rc = iscsi_conn_open_lun(conn, i, lun); + if (rc != 0) { + goto error; + } + } + + return; + +error: + iscsi_conn_close_luns(conn); +} + +/** + * This function will stop executing the specified connection. + */ +static void +iscsi_conn_stop(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_tgt_node *target; + + assert(conn->state == ISCSI_CONN_STATE_EXITED); + assert(conn->data_in_cnt == 0); + assert(conn->data_out_cnt == 0); + + if (conn->sess != NULL && + conn->sess->session_type == SESSION_TYPE_NORMAL && + conn->full_feature) { + target = conn->sess->target; + pthread_mutex_lock(&target->mutex); + target->num_active_conns--; + pthread_mutex_unlock(&target->mutex); + + iscsi_conn_close_luns(conn); + } + + assert(spdk_io_channel_get_thread(spdk_io_channel_from_ctx(conn->pg)) == + spdk_get_thread()); +} + +static int +_iscsi_conn_check_shutdown(void *arg) +{ + struct spdk_iscsi_conn *conn = arg; + int rc; + + rc = iscsi_conn_free_tasks(conn); + if (rc < 0) { + return SPDK_POLLER_BUSY; + } + + spdk_poller_unregister(&conn->shutdown_timer); + + iscsi_conn_stop(conn); + iscsi_conn_free(conn); + + return SPDK_POLLER_BUSY; +} + +static void +_iscsi_conn_destruct(struct spdk_iscsi_conn *conn) +{ + int rc; + + iscsi_poll_group_remove_conn(conn->pg, conn); + spdk_sock_close(&conn->sock); + iscsi_clear_all_transfer_task(conn, NULL, NULL); + spdk_poller_unregister(&conn->logout_request_timer); + spdk_poller_unregister(&conn->logout_timer); + + rc = iscsi_conn_free_tasks(conn); + if (rc < 0) { + /* The connection cannot be freed yet. Check back later. */ + conn->shutdown_timer = SPDK_POLLER_REGISTER(_iscsi_conn_check_shutdown, conn, 1000); + } else { + iscsi_conn_stop(conn); + iscsi_conn_free(conn); + } +} + +static int +_iscsi_conn_check_pending_tasks(void *arg) +{ + struct spdk_iscsi_conn *conn = arg; + + if (conn->dev != NULL && + spdk_scsi_dev_has_pending_tasks(conn->dev, conn->initiator_port)) { + return SPDK_POLLER_BUSY; + } + + spdk_poller_unregister(&conn->shutdown_timer); + + _iscsi_conn_destruct(conn); + + return SPDK_POLLER_BUSY; +} + +void +iscsi_conn_destruct(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_pdu *pdu; + struct spdk_iscsi_task *task; + int opcode; + + /* If a connection is already in exited status, just return */ + if (conn->state >= ISCSI_CONN_STATE_EXITED) { + return; + } + + conn->state = ISCSI_CONN_STATE_EXITED; + + /* + * Each connection pre-allocates its next PDU - make sure these get + * freed here. + */ + pdu = conn->pdu_in_progress; + if (pdu) { + /* remove the task left in the PDU too. */ + task = pdu->task; + if (task) { + opcode = pdu->bhs.opcode; + switch (opcode) { + case ISCSI_OP_SCSI: + case ISCSI_OP_SCSI_DATAOUT: + spdk_scsi_task_process_abort(&task->scsi); + iscsi_task_cpl(&task->scsi); + break; + default: + SPDK_ERRLOG("unexpected opcode %x\n", opcode); + iscsi_task_put(task); + break; + } + } + iscsi_put_pdu(pdu); + conn->pdu_in_progress = NULL; + } + + if (conn->sess != NULL && conn->pending_task_cnt > 0) { + iscsi_conn_cleanup_backend(conn); + } + + if (conn->dev != NULL && + spdk_scsi_dev_has_pending_tasks(conn->dev, conn->initiator_port)) { + conn->shutdown_timer = SPDK_POLLER_REGISTER(_iscsi_conn_check_pending_tasks, conn, 1000); + } else { + _iscsi_conn_destruct(conn); + } +} + +int +iscsi_get_active_conns(struct spdk_iscsi_tgt_node *target) +{ + struct spdk_iscsi_conn *conn; + int num = 0; + + if (g_conns_array == MAP_FAILED) { + return 0; + } + + pthread_mutex_lock(&g_conns_mutex); + TAILQ_FOREACH(conn, &g_active_conns, conn_link) { + if (target == NULL || conn->target == target) { + num++; + } + } + pthread_mutex_unlock(&g_conns_mutex); + return num; +} + +static void +iscsi_conn_check_shutdown_cb(void *arg1) +{ + _iscsi_conns_cleanup(); + shutdown_iscsi_conns_done(); +} + +static int +iscsi_conn_check_shutdown(void *arg) +{ + if (iscsi_get_active_conns(NULL) != 0) { + return SPDK_POLLER_BUSY; + } + + spdk_poller_unregister(&g_shutdown_timer); + + spdk_thread_send_msg(spdk_get_thread(), iscsi_conn_check_shutdown_cb, NULL); + + return SPDK_POLLER_BUSY; +} + +static void +iscsi_send_logout_request(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_async *rsph; + + rsp_pdu = iscsi_get_pdu(conn); + assert(rsp_pdu != NULL); + + rsph = (struct iscsi_bhs_async *)&rsp_pdu->bhs; + rsp_pdu->data = NULL; + + rsph->opcode = ISCSI_OP_ASYNC; + to_be32(&rsph->ffffffff, 0xFFFFFFFF); + rsph->async_event = 1; + to_be16(&rsph->param3, ISCSI_LOGOUT_REQUEST_TIMEOUT); + + to_be32(&rsph->stat_sn, conn->StatSN); + to_be32(&rsph->exp_cmd_sn, conn->sess->ExpCmdSN); + to_be32(&rsph->max_cmd_sn, conn->sess->MaxCmdSN); + + iscsi_conn_write_pdu(conn, rsp_pdu, iscsi_conn_pdu_generic_complete, NULL); +} + +static int +logout_request_timeout(void *arg) +{ + struct spdk_iscsi_conn *conn = arg; + + if (conn->state < ISCSI_CONN_STATE_EXITING) { + conn->state = ISCSI_CONN_STATE_EXITING; + } + + return SPDK_POLLER_BUSY; +} + +/* If the connection is running and logout is not requested yet, request logout + * to initiator and wait for the logout process to start. + */ +static void +_iscsi_conn_request_logout(void *ctx) +{ + struct spdk_iscsi_conn *conn = ctx; + + if (conn->state > ISCSI_CONN_STATE_RUNNING || + conn->logout_request_timer != NULL) { + return; + } + + iscsi_send_logout_request(conn); + + conn->logout_request_timer = SPDK_POLLER_REGISTER(logout_request_timeout, + conn, ISCSI_LOGOUT_REQUEST_TIMEOUT * 1000000); +} + +static void +iscsi_conn_request_logout(struct spdk_iscsi_conn *conn) +{ + struct spdk_thread *thread; + + if (conn->state == ISCSI_CONN_STATE_INVALID) { + /* Move it to EXITING state if the connection is in login. */ + conn->state = ISCSI_CONN_STATE_EXITING; + } else if (conn->state == ISCSI_CONN_STATE_RUNNING && + conn->logout_request_timer == NULL) { + thread = spdk_io_channel_get_thread(spdk_io_channel_from_ctx(conn->pg)); + spdk_thread_send_msg(thread, _iscsi_conn_request_logout, conn); + } +} + +void +iscsi_conns_request_logout(struct spdk_iscsi_tgt_node *target) +{ + struct spdk_iscsi_conn *conn; + + if (g_conns_array == MAP_FAILED) { + return; + } + + pthread_mutex_lock(&g_conns_mutex); + TAILQ_FOREACH(conn, &g_active_conns, conn_link) { + if (target == NULL || conn->target == target) { + iscsi_conn_request_logout(conn); + } + } + pthread_mutex_unlock(&g_conns_mutex); +} + +void +shutdown_iscsi_conns(void) +{ + iscsi_conns_request_logout(NULL); + + g_shutdown_timer = SPDK_POLLER_REGISTER(iscsi_conn_check_shutdown, NULL, 1000); +} + +/* Do not set conn->state if the connection has already started exiting. + * This ensures we do not move a connection from EXITED state back to EXITING. + */ +static void +_iscsi_conn_drop(void *ctx) +{ + struct spdk_iscsi_conn *conn = ctx; + + if (conn->state < ISCSI_CONN_STATE_EXITING) { + conn->state = ISCSI_CONN_STATE_EXITING; + } +} + +int +iscsi_drop_conns(struct spdk_iscsi_conn *conn, const char *conn_match, + int drop_all) +{ + struct spdk_iscsi_conn *xconn; + const char *xconn_match; + struct spdk_thread *thread; + int num; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "iscsi_drop_conns\n"); + + num = 0; + pthread_mutex_lock(&g_conns_mutex); + if (g_conns_array == MAP_FAILED) { + goto exit; + } + + TAILQ_FOREACH(xconn, &g_active_conns, conn_link) { + if (xconn == conn) { + continue; + } + + if (!drop_all && xconn->initiator_port == NULL) { + continue; + } + + xconn_match = + drop_all ? xconn->initiator_name : spdk_scsi_port_get_name(xconn->initiator_port); + + if (!strcasecmp(conn_match, xconn_match) && + conn->target == xconn->target) { + + if (num == 0) { + /* + * Only print this message before we report the + * first dropped connection. + */ + SPDK_ERRLOG("drop old connections %s by %s\n", + conn->target->name, conn_match); + } + + SPDK_ERRLOG("exiting conn by %s (%s)\n", + xconn_match, xconn->initiator_addr); + if (xconn->sess != NULL) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "TSIH=%u\n", xconn->sess->tsih); + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "TSIH=xx\n"); + } + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "CID=%u\n", xconn->cid); + + thread = spdk_io_channel_get_thread(spdk_io_channel_from_ctx(xconn->pg)); + spdk_thread_send_msg(thread, _iscsi_conn_drop, xconn); + + num++; + } + } + +exit: + pthread_mutex_unlock(&g_conns_mutex); + + if (num != 0) { + SPDK_ERRLOG("exiting %d conns\n", num); + } + + return 0; +} + +static int +_iscsi_conn_abort_queued_datain_task(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task) +{ + struct spdk_iscsi_task *subtask; + uint32_t remaining_size; + + if (conn->data_in_cnt >= MAX_LARGE_DATAIN_PER_CONNECTION) { + return -1; + } + + assert(task->current_datain_offset <= task->scsi.transfer_len); + /* Stop split and abort read I/O for remaining data. */ + if (task->current_datain_offset < task->scsi.transfer_len) { + remaining_size = task->scsi.transfer_len - task->current_datain_offset; + subtask = iscsi_task_get(conn, task, iscsi_task_cpl); + assert(subtask != NULL); + subtask->scsi.offset = task->current_datain_offset; + subtask->scsi.length = remaining_size; + spdk_scsi_task_set_data(&subtask->scsi, NULL, 0); + task->current_datain_offset += subtask->scsi.length; + + subtask->scsi.transfer_len = subtask->scsi.length; + spdk_scsi_task_process_abort(&subtask->scsi); + iscsi_task_cpl(&subtask->scsi); + } + + /* Remove the primary task from the list because all subtasks are submitted + * or aborted. + */ + assert(task->current_datain_offset == task->scsi.transfer_len); + TAILQ_REMOVE(&conn->queued_datain_tasks, task, link); + return 0; +} + +int +iscsi_conn_abort_queued_datain_task(struct spdk_iscsi_conn *conn, + uint32_t ref_task_tag) +{ + struct spdk_iscsi_task *task; + + TAILQ_FOREACH(task, &conn->queued_datain_tasks, link) { + if (task->tag == ref_task_tag) { + return _iscsi_conn_abort_queued_datain_task(conn, task); + } + } + + return 0; +} + +int +iscsi_conn_abort_queued_datain_tasks(struct spdk_iscsi_conn *conn, + struct spdk_scsi_lun *lun, + struct spdk_iscsi_pdu *pdu) +{ + struct spdk_iscsi_task *task, *task_tmp; + struct spdk_iscsi_pdu *pdu_tmp; + int rc; + + TAILQ_FOREACH_SAFE(task, &conn->queued_datain_tasks, link, task_tmp) { + pdu_tmp = iscsi_task_get_pdu(task); + if ((lun == NULL || lun == task->scsi.lun) && + (pdu == NULL || (spdk_sn32_lt(pdu_tmp->cmd_sn, pdu->cmd_sn)))) { + rc = _iscsi_conn_abort_queued_datain_task(conn, task); + if (rc != 0) { + return rc; + } + } + } + + return 0; +} + +int +iscsi_conn_handle_queued_datain_tasks(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_task *task; + + while (!TAILQ_EMPTY(&conn->queued_datain_tasks) && + conn->data_in_cnt < MAX_LARGE_DATAIN_PER_CONNECTION) { + task = TAILQ_FIRST(&conn->queued_datain_tasks); + assert(task->current_datain_offset <= task->scsi.transfer_len); + if (task->current_datain_offset < task->scsi.transfer_len) { + struct spdk_iscsi_task *subtask; + uint32_t remaining_size = 0; + + remaining_size = task->scsi.transfer_len - task->current_datain_offset; + subtask = iscsi_task_get(conn, task, iscsi_task_cpl); + assert(subtask != NULL); + subtask->scsi.offset = task->current_datain_offset; + spdk_scsi_task_set_data(&subtask->scsi, NULL, 0); + + if (spdk_scsi_dev_get_lun(conn->dev, task->lun_id) == NULL) { + /* Stop submitting split read I/Os for remaining data. */ + TAILQ_REMOVE(&conn->queued_datain_tasks, task, link); + task->current_datain_offset += remaining_size; + assert(task->current_datain_offset == task->scsi.transfer_len); + subtask->scsi.transfer_len = remaining_size; + spdk_scsi_task_process_null_lun(&subtask->scsi); + iscsi_task_cpl(&subtask->scsi); + return 0; + } + + subtask->scsi.length = spdk_min(SPDK_BDEV_LARGE_BUF_MAX_SIZE, remaining_size); + task->current_datain_offset += subtask->scsi.length; + iscsi_queue_task(conn, subtask); + } + if (task->current_datain_offset == task->scsi.transfer_len) { + TAILQ_REMOVE(&conn->queued_datain_tasks, task, link); + } + } + return 0; +} + +void +iscsi_task_mgmt_cpl(struct spdk_scsi_task *scsi_task) +{ + struct spdk_iscsi_task *task = iscsi_task_from_scsi_task(scsi_task); + + iscsi_task_mgmt_response(task->conn, task); + iscsi_task_put(task); +} + +static void +iscsi_task_copy_to_rsp_scsi_status(struct spdk_iscsi_task *primary, + struct spdk_scsi_task *task) +{ + memcpy(primary->rsp_sense_data, task->sense_data, task->sense_data_len); + primary->rsp_sense_data_len = task->sense_data_len; + primary->rsp_scsi_status = task->status; +} + +static void +iscsi_task_copy_from_rsp_scsi_status(struct spdk_scsi_task *task, + struct spdk_iscsi_task *primary) +{ + memcpy(task->sense_data, primary->rsp_sense_data, + primary->rsp_sense_data_len); + task->sense_data_len = primary->rsp_sense_data_len; + task->status = primary->rsp_scsi_status; +} + +static void +process_completed_read_subtask_list(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *primary) +{ + struct spdk_iscsi_task *subtask, *tmp; + + TAILQ_FOREACH_SAFE(subtask, &primary->subtask_list, subtask_link, tmp) { + if (subtask->scsi.offset == primary->bytes_completed) { + TAILQ_REMOVE(&primary->subtask_list, subtask, subtask_link); + primary->bytes_completed += subtask->scsi.length; + iscsi_task_response(conn, subtask); + iscsi_task_put(subtask); + } else { + break; + } + } + + if (primary->bytes_completed == primary->scsi.transfer_len) { + iscsi_task_put(primary); + } +} + +static void +process_read_task_completion(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task, + struct spdk_iscsi_task *primary) +{ + struct spdk_iscsi_task *tmp; + + /* If the status of the completed subtask is the first failure, + * copy it to out-of-order subtasks and remember it as the status + * of the command, + * + * Even if the status of the completed task is success, + * there are any failed subtask ever, copy the first failed status + * to it. + */ + if (task->scsi.status != SPDK_SCSI_STATUS_GOOD) { + if (primary->rsp_scsi_status == SPDK_SCSI_STATUS_GOOD) { + TAILQ_FOREACH(tmp, &primary->subtask_list, subtask_link) { + spdk_scsi_task_copy_status(&tmp->scsi, &task->scsi); + } + iscsi_task_copy_to_rsp_scsi_status(primary, &task->scsi); + } + } else if (primary->rsp_scsi_status != SPDK_SCSI_STATUS_GOOD) { + iscsi_task_copy_from_rsp_scsi_status(&task->scsi, primary); + } + + if (task == primary) { + primary->bytes_completed = task->scsi.length; + /* For non split read I/O */ + assert(primary->bytes_completed == task->scsi.transfer_len); + iscsi_task_response(conn, task); + iscsi_task_put(task); + } else { + if (task->scsi.offset != primary->bytes_completed) { + TAILQ_FOREACH(tmp, &primary->subtask_list, subtask_link) { + if (task->scsi.offset < tmp->scsi.offset) { + TAILQ_INSERT_BEFORE(tmp, task, subtask_link); + return; + } + } + + TAILQ_INSERT_TAIL(&primary->subtask_list, task, subtask_link); + } else { + TAILQ_INSERT_HEAD(&primary->subtask_list, task, subtask_link); + process_completed_read_subtask_list(conn, primary); + } + } +} + +static void +process_non_read_task_completion(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task, + struct spdk_iscsi_task *primary) +{ + primary->bytes_completed += task->scsi.length; + + /* If the status of the subtask is the first failure, remember it as + * the status of the command and set it to the status of the primary + * task later. + * + * If the first failed task is the primary, two copies can be avoided + * but code simplicity is prioritized. + */ + if (task->scsi.status == SPDK_SCSI_STATUS_GOOD) { + if (task != primary) { + primary->scsi.data_transferred += task->scsi.data_transferred; + } + } else if (primary->rsp_scsi_status == SPDK_SCSI_STATUS_GOOD) { + iscsi_task_copy_to_rsp_scsi_status(primary, &task->scsi); + } + + if (primary->bytes_completed == primary->scsi.transfer_len) { + /* + * Check if this is the last task completed for an iSCSI write + * that required child subtasks. If task != primary, we know + * for sure that it was part of an iSCSI write with child subtasks. + * The trickier case is when the last task completed was the initial + * task - in this case the task will have a smaller length than + * the overall transfer length. + */ + if (task != primary || task->scsi.length != task->scsi.transfer_len) { + /* If LUN is removed in the middle of the iSCSI write sequence, + * primary might complete the write to the initiator because it is not + * ensured that the initiator will send all data requested by R2Ts. + * + * We check it and skip the following if primary is completed. (see + * iscsi_clear_all_transfer_task() in iscsi.c.) + */ + if (primary->is_r2t_active) { + if (primary->rsp_scsi_status != SPDK_SCSI_STATUS_GOOD) { + iscsi_task_copy_from_rsp_scsi_status(&primary->scsi, primary); + } + iscsi_task_response(conn, primary); + iscsi_del_transfer_task(conn, primary->tag); + } + } else { + iscsi_task_response(conn, task); + } + } + iscsi_task_put(task); +} + +void +iscsi_task_cpl(struct spdk_scsi_task *scsi_task) +{ + struct spdk_iscsi_task *primary; + struct spdk_iscsi_task *task = iscsi_task_from_scsi_task(scsi_task); + struct spdk_iscsi_conn *conn = task->conn; + struct spdk_iscsi_pdu *pdu = task->pdu; + + spdk_trace_record(TRACE_ISCSI_TASK_DONE, conn->id, 0, (uintptr_t)task, 0); + + task->is_queued = false; + primary = iscsi_task_get_primary(task); + + if (iscsi_task_is_read(primary)) { + process_read_task_completion(conn, task, primary); + } else { + process_non_read_task_completion(conn, task, primary); + } + if (!task->parent) { + spdk_trace_record(TRACE_ISCSI_PDU_COMPLETED, 0, 0, (uintptr_t)pdu, 0); + } +} + +static void +iscsi_conn_send_nopin(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_nop_in *rsp; + /* Only send nopin if we have logged in and are in a normal session. */ + if (conn->sess == NULL || + !conn->full_feature || + !iscsi_param_eq_val(conn->sess->params, "SessionType", "Normal")) { + return; + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "send NOPIN isid=%"PRIx64", tsih=%u, cid=%u\n", + conn->sess->isid, conn->sess->tsih, conn->cid); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "StatSN=%u, ExpCmdSN=%u, MaxCmdSN=%u\n", + conn->StatSN, conn->sess->ExpCmdSN, + conn->sess->MaxCmdSN); + rsp_pdu = iscsi_get_pdu(conn); + rsp = (struct iscsi_bhs_nop_in *) &rsp_pdu->bhs; + rsp_pdu->data = NULL; + /* + * iscsi_get_pdu() memset's the PDU for us, so only fill out the needed + * fields. + */ + rsp->opcode = ISCSI_OP_NOPIN; + rsp->flags = 0x80; + /* + * Technically the to_be32() is not needed here, since + * to_be32(0xFFFFFFFU) returns 0xFFFFFFFFU. + */ + to_be32(&rsp->itt, 0xFFFFFFFFU); + to_be32(&rsp->ttt, conn->id); + to_be32(&rsp->stat_sn, conn->StatSN); + to_be32(&rsp->exp_cmd_sn, conn->sess->ExpCmdSN); + to_be32(&rsp->max_cmd_sn, conn->sess->MaxCmdSN); + iscsi_conn_write_pdu(conn, rsp_pdu, iscsi_conn_pdu_generic_complete, NULL); + conn->last_nopin = spdk_get_ticks(); + conn->nop_outstanding = true; +} + +void +iscsi_conn_handle_nop(struct spdk_iscsi_conn *conn) +{ + uint64_t tsc; + + /** + * This function will be executed by nop_poller of iSCSI polling group, so + * we need to check the connection state first, then do the nop interval + * expiration check work. + */ + if ((conn->state == ISCSI_CONN_STATE_EXITED) || + (conn->state == ISCSI_CONN_STATE_EXITING)) { + return; + } + + /* Check for nop interval expiration */ + tsc = spdk_get_ticks(); + if (conn->nop_outstanding) { + if ((tsc - conn->last_nopin) > conn->timeout) { + SPDK_ERRLOG("Timed out waiting for NOP-Out response from initiator\n"); + SPDK_ERRLOG(" tsc=0x%lx, last_nopin=0x%lx\n", tsc, conn->last_nopin); + SPDK_ERRLOG(" initiator=%s, target=%s\n", conn->initiator_name, + conn->target_short_name); + conn->state = ISCSI_CONN_STATE_EXITING; + } + } else if (tsc - conn->last_nopin > conn->nopininterval) { + iscsi_conn_send_nopin(conn); + } +} + +/** + * \brief Reads data for the specified iSCSI connection from its TCP socket. + * + * The TCP socket is marked as non-blocking, so this function may not read + * all data requested. + * + * Returns SPDK_ISCSI_CONNECTION_FATAL if the recv() operation indicates a fatal + * error with the TCP connection (including if the TCP connection was closed + * unexpectedly. + * + * Otherwise returns the number of bytes successfully read. + */ +int +iscsi_conn_read_data(struct spdk_iscsi_conn *conn, int bytes, + void *buf) +{ + int ret; + + if (bytes == 0) { + return 0; + } + + ret = spdk_sock_recv(conn->sock, buf, bytes); + + if (ret > 0) { + spdk_trace_record(TRACE_ISCSI_READ_FROM_SOCKET_DONE, conn->id, ret, 0, 0); + return ret; + } + + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + + /* For connect reset issue, do not output error log */ + if (errno == ECONNRESET) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_sock_recv() failed, errno %d: %s\n", + errno, spdk_strerror(errno)); + } else { + SPDK_ERRLOG("spdk_sock_recv() failed, errno %d: %s\n", + errno, spdk_strerror(errno)); + } + } + + /* connection closed */ + return SPDK_ISCSI_CONNECTION_FATAL; +} + +int +iscsi_conn_readv_data(struct spdk_iscsi_conn *conn, + struct iovec *iov, int iovcnt) +{ + int ret; + + if (iov == NULL || iovcnt == 0) { + return 0; + } + + if (iovcnt == 1) { + return iscsi_conn_read_data(conn, iov[0].iov_len, + iov[0].iov_base); + } + + ret = spdk_sock_readv(conn->sock, iov, iovcnt); + + if (ret > 0) { + spdk_trace_record(TRACE_ISCSI_READ_FROM_SOCKET_DONE, conn->id, ret, 0, 0); + return ret; + } + + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } + + /* For connect reset issue, do not output error log */ + if (errno == ECONNRESET) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_sock_readv() failed, errno %d: %s\n", + errno, spdk_strerror(errno)); + } else { + SPDK_ERRLOG("spdk_sock_readv() failed, errno %d: %s\n", + errno, spdk_strerror(errno)); + } + } + + /* connection closed */ + return SPDK_ISCSI_CONNECTION_FATAL; +} + +static bool +iscsi_is_free_pdu_deferred(struct spdk_iscsi_pdu *pdu) +{ + if (pdu == NULL) { + return false; + } + + if (pdu->bhs.opcode == ISCSI_OP_R2T || + pdu->bhs.opcode == ISCSI_OP_SCSI_DATAIN) { + return true; + } + + return false; +} + +static int +iscsi_dif_verify(struct spdk_iscsi_pdu *pdu, struct spdk_dif_ctx *dif_ctx) +{ + struct iovec iov; + struct spdk_dif_error err_blk = {}; + uint32_t num_blocks; + int rc; + + iov.iov_base = pdu->data; + iov.iov_len = pdu->data_buf_len; + num_blocks = pdu->data_buf_len / dif_ctx->block_size; + + rc = spdk_dif_verify(&iov, 1, num_blocks, dif_ctx, &err_blk); + if (rc != 0) { + SPDK_ERRLOG("DIF error detected. type=%d, offset=%" PRIu32 "\n", + err_blk.err_type, err_blk.err_offset); + } + + return rc; +} + +static void +_iscsi_conn_pdu_write_done(void *cb_arg, int err) +{ + struct spdk_iscsi_pdu *pdu = cb_arg; + struct spdk_iscsi_conn *conn = pdu->conn; + + assert(conn != NULL); + + if (spdk_unlikely(conn->state >= ISCSI_CONN_STATE_EXITING)) { + /* The other policy will recycle the resource */ + return; + } + + TAILQ_REMOVE(&conn->write_pdu_list, pdu, tailq); + + if (err != 0) { + conn->state = ISCSI_CONN_STATE_EXITING; + } else { + spdk_trace_record(TRACE_ISCSI_FLUSH_WRITEBUF_DONE, conn->id, pdu->mapped_length, (uintptr_t)pdu, 0); + } + + if ((conn->full_feature) && + (conn->sess->ErrorRecoveryLevel >= 1) && + iscsi_is_free_pdu_deferred(pdu)) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "stat_sn=%d\n", + from_be32(&pdu->bhs.stat_sn)); + TAILQ_INSERT_TAIL(&conn->snack_pdu_list, pdu, + tailq); + } else { + iscsi_conn_free_pdu(conn, pdu); + } +} + +void +iscsi_conn_pdu_generic_complete(void *cb_arg) +{ +} + +void +iscsi_conn_write_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu, + iscsi_conn_xfer_complete_cb cb_fn, + void *cb_arg) +{ + uint32_t crc32c; + ssize_t rc; + + if (spdk_unlikely(pdu->dif_insert_or_strip)) { + rc = iscsi_dif_verify(pdu, &pdu->dif_ctx); + if (rc != 0) { + iscsi_conn_free_pdu(conn, pdu); + conn->state = ISCSI_CONN_STATE_EXITING; + return; + } + } + + if (pdu->bhs.opcode != ISCSI_OP_LOGIN_RSP) { + /* Header Digest */ + if (conn->header_digest) { + crc32c = iscsi_pdu_calc_header_digest(pdu); + MAKE_DIGEST_WORD(pdu->header_digest, crc32c); + } + + /* Data Digest */ + if (conn->data_digest && DGET24(pdu->bhs.data_segment_len) != 0) { + crc32c = iscsi_pdu_calc_data_digest(pdu); + MAKE_DIGEST_WORD(pdu->data_digest, crc32c); + } + } + + pdu->cb_fn = cb_fn; + pdu->cb_arg = cb_arg; + TAILQ_INSERT_TAIL(&conn->write_pdu_list, pdu, tailq); + + if (spdk_unlikely(conn->state >= ISCSI_CONN_STATE_EXITING)) { + return; + } + pdu->sock_req.iovcnt = iscsi_build_iovs(conn, pdu->iov, SPDK_COUNTOF(pdu->iov), pdu, + &pdu->mapped_length); + pdu->sock_req.cb_fn = _iscsi_conn_pdu_write_done; + pdu->sock_req.cb_arg = pdu; + + spdk_trace_record(TRACE_ISCSI_FLUSH_WRITEBUF_START, conn->id, pdu->mapped_length, (uintptr_t)pdu, + pdu->sock_req.iovcnt); + spdk_sock_writev_async(conn->sock, &pdu->sock_req); +} + +static void +iscsi_conn_sock_cb(void *arg, struct spdk_sock_group *group, struct spdk_sock *sock) +{ + struct spdk_iscsi_conn *conn = arg; + int rc; + + assert(conn != NULL); + + if ((conn->state == ISCSI_CONN_STATE_EXITED) || + (conn->state == ISCSI_CONN_STATE_EXITING)) { + return; + } + + /* Handle incoming PDUs */ + rc = iscsi_handle_incoming_pdus(conn); + if (rc < 0) { + conn->state = ISCSI_CONN_STATE_EXITING; + } +} + +static void +iscsi_conn_full_feature_migrate(void *arg) +{ + struct spdk_iscsi_conn *conn = arg; + + if (conn->state >= ISCSI_CONN_STATE_EXITING) { + /* Connection is being exited before this callback is executed. */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Connection is already exited.\n"); + return; + } + + if (conn->sess->session_type == SESSION_TYPE_NORMAL) { + iscsi_conn_open_luns(conn); + } + + /* Add this connection to the assigned poll group. */ + iscsi_poll_group_add_conn(conn->pg, conn); +} + +static struct spdk_iscsi_poll_group *g_next_pg = NULL; + +void +iscsi_conn_schedule(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_poll_group *pg; + struct spdk_iscsi_tgt_node *target; + + if (conn->sess->session_type != SESSION_TYPE_NORMAL) { + /* Leave all non-normal sessions on the acceptor + * thread. */ + return; + } + pthread_mutex_lock(&g_iscsi.mutex); + + target = conn->sess->target; + pthread_mutex_lock(&target->mutex); + target->num_active_conns++; + if (target->num_active_conns == 1) { + /** + * This is the only active connection for this target node. + * Pick a poll group using round-robin. + */ + if (g_next_pg == NULL) { + g_next_pg = TAILQ_FIRST(&g_iscsi.poll_group_head); + assert(g_next_pg != NULL); + } + + pg = g_next_pg; + g_next_pg = TAILQ_NEXT(g_next_pg, link); + + /* Save the pg in the target node so it can be used for any other connections to this target node. */ + target->pg = pg; + } else { + /** + * There are other active connections for this target node. + */ + pg = target->pg; + } + + pthread_mutex_unlock(&target->mutex); + pthread_mutex_unlock(&g_iscsi.mutex); + + assert(spdk_io_channel_get_thread(spdk_io_channel_from_ctx(conn->pg)) == + spdk_get_thread()); + + /* Remove this connection from the previous poll group */ + iscsi_poll_group_remove_conn(conn->pg, conn); + + conn->last_nopin = spdk_get_ticks(); + conn->pg = pg; + + spdk_thread_send_msg(spdk_io_channel_get_thread(spdk_io_channel_from_ctx(pg)), + iscsi_conn_full_feature_migrate, conn); +} + +static int +logout_timeout(void *arg) +{ + struct spdk_iscsi_conn *conn = arg; + + if (conn->state < ISCSI_CONN_STATE_EXITING) { + conn->state = ISCSI_CONN_STATE_EXITING; + } + + return SPDK_POLLER_BUSY; +} + +void +iscsi_conn_logout(struct spdk_iscsi_conn *conn) +{ + conn->is_logged_out = true; + conn->logout_timer = SPDK_POLLER_REGISTER(logout_timeout, conn, ISCSI_LOGOUT_TIMEOUT * 1000000); +} + +SPDK_TRACE_REGISTER_FN(iscsi_conn_trace, "iscsi_conn", TRACE_GROUP_ISCSI) +{ + spdk_trace_register_owner(OWNER_ISCSI_CONN, 'c'); + spdk_trace_register_object(OBJECT_ISCSI_PDU, 'p'); + spdk_trace_register_description("ISCSI_READ_DONE", TRACE_ISCSI_READ_FROM_SOCKET_DONE, + OWNER_ISCSI_CONN, OBJECT_NONE, 0, 0, ""); + spdk_trace_register_description("ISCSI_WRITE_START", TRACE_ISCSI_FLUSH_WRITEBUF_START, + OWNER_ISCSI_CONN, OBJECT_NONE, 0, 0, "iovec: "); + spdk_trace_register_description("ISCSI_WRITE_DONE", TRACE_ISCSI_FLUSH_WRITEBUF_DONE, + OWNER_ISCSI_CONN, OBJECT_NONE, 0, 0, ""); + spdk_trace_register_description("ISCSI_READ_PDU", TRACE_ISCSI_READ_PDU, + OWNER_ISCSI_CONN, OBJECT_ISCSI_PDU, 1, 0, "opc: "); + spdk_trace_register_description("ISCSI_TASK_DONE", TRACE_ISCSI_TASK_DONE, + OWNER_ISCSI_CONN, OBJECT_SCSI_TASK, 0, 0, ""); + spdk_trace_register_description("ISCSI_TASK_QUEUE", TRACE_ISCSI_TASK_QUEUE, + OWNER_ISCSI_CONN, OBJECT_SCSI_TASK, 1, 1, "pdu: "); + spdk_trace_register_description("ISCSI_TASK_EXECUTED", TRACE_ISCSI_TASK_EXECUTED, + OWNER_ISCSI_CONN, OBJECT_ISCSI_PDU, 0, 0, ""); + spdk_trace_register_description("ISCSI_PDU_COMPLETED", TRACE_ISCSI_PDU_COMPLETED, + OWNER_ISCSI_CONN, OBJECT_ISCSI_PDU, 0, 0, ""); +} + +void +iscsi_conn_info_json(struct spdk_json_write_ctx *w, struct spdk_iscsi_conn *conn) +{ + uint16_t tsih; + + if (!conn->is_valid) { + return; + } + + spdk_json_write_object_begin(w); + + spdk_json_write_named_int32(w, "id", conn->id); + + spdk_json_write_named_int32(w, "cid", conn->cid); + + /* + * If we try to return data for a connection that has not + * logged in yet, the session will not be set. So in this + * case, return -1 for the tsih rather than segfaulting + * on the null conn->sess. + */ + if (conn->sess == NULL) { + tsih = -1; + } else { + tsih = conn->sess->tsih; + } + spdk_json_write_named_int32(w, "tsih", tsih); + + spdk_json_write_named_string(w, "initiator_addr", conn->initiator_addr); + + spdk_json_write_named_string(w, "target_addr", conn->target_addr); + + spdk_json_write_named_string(w, "target_node_name", conn->target_short_name); + + spdk_json_write_named_string(w, "thread_name", + spdk_thread_get_name(spdk_get_thread())); + + spdk_json_write_object_end(w); +} diff --git a/src/spdk/lib/iscsi/conn.h b/src/spdk/lib/iscsi/conn.h new file mode 100644 index 000000000..a85d2ddeb --- /dev/null +++ b/src/spdk/lib/iscsi/conn.h @@ -0,0 +1,237 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SPDK_ISCSI_CONN_H +#define SPDK_ISCSI_CONN_H + +#include "spdk/stdinc.h" + +#include "iscsi/iscsi.h" +#include "spdk/queue.h" +#include "spdk/cpuset.h" +#include "spdk/scsi.h" + +/* + * MAX_CONNECTION_PARAMS: The numbers of the params in conn_param_table + * MAX_SESSION_PARAMS: The numbers of the params in sess_param_table + */ +#define MAX_CONNECTION_PARAMS 14 +#define MAX_SESSION_PARAMS 19 + +#define MAX_ADDRBUF 64 +#define MAX_INITIATOR_ADDR (MAX_ADDRBUF) +#define MAX_TARGET_ADDR (MAX_ADDRBUF) + +#define OWNER_ISCSI_CONN 0x1 + +#define OBJECT_ISCSI_PDU 0x1 + +#define TRACE_GROUP_ISCSI 0x1 +#define TRACE_ISCSI_READ_FROM_SOCKET_DONE SPDK_TPOINT_ID(TRACE_GROUP_ISCSI, 0x0) +#define TRACE_ISCSI_FLUSH_WRITEBUF_START SPDK_TPOINT_ID(TRACE_GROUP_ISCSI, 0x1) +#define TRACE_ISCSI_FLUSH_WRITEBUF_DONE SPDK_TPOINT_ID(TRACE_GROUP_ISCSI, 0x2) +#define TRACE_ISCSI_READ_PDU SPDK_TPOINT_ID(TRACE_GROUP_ISCSI, 0x3) +#define TRACE_ISCSI_TASK_DONE SPDK_TPOINT_ID(TRACE_GROUP_ISCSI, 0x4) +#define TRACE_ISCSI_TASK_QUEUE SPDK_TPOINT_ID(TRACE_GROUP_ISCSI, 0x5) +#define TRACE_ISCSI_TASK_EXECUTED SPDK_TPOINT_ID(TRACE_GROUP_ISCSI, 0x6) +#define TRACE_ISCSI_PDU_COMPLETED SPDK_TPOINT_ID(TRACE_GROUP_ISCSI, 0x7) + +enum iscsi_pdu_recv_state { + /* Ready to wait for PDU */ + ISCSI_PDU_RECV_STATE_AWAIT_PDU_READY, + + /* Active connection waiting for any PDU header */ + ISCSI_PDU_RECV_STATE_AWAIT_PDU_HDR, + + /* Active connection waiting for payload */ + ISCSI_PDU_RECV_STATE_AWAIT_PDU_PAYLOAD, + + /* Active connection does not wait for payload */ + ISCSI_PDU_RECV_STATE_ERROR, +}; + +struct spdk_poller; +struct spdk_iscsi_conn; + +struct spdk_iscsi_lun { + struct spdk_iscsi_conn *conn; + struct spdk_scsi_lun *lun; + struct spdk_scsi_lun_desc *desc; + struct spdk_poller *remove_poller; +}; + +struct spdk_iscsi_conn { + int id; + int is_valid; + /* + * All fields below this point are reinitialized each time the + * connection object is allocated. Make sure to update the + * SPDK_ISCSI_CONNECTION_MEMSET() macro if changing which fields + * are initialized when allocated. + */ + struct spdk_iscsi_portal *portal; + int pg_tag; + char portal_host[MAX_PORTAL_ADDR + 1]; + char portal_port[MAX_PORTAL_ADDR + 1]; + struct spdk_iscsi_poll_group *pg; + struct spdk_sock *sock; + struct spdk_iscsi_sess *sess; + + enum iscsi_connection_state state; + int login_phase; + bool is_logged_out; + struct spdk_iscsi_pdu *login_rsp_pdu; + + uint64_t last_flush; + uint64_t last_fill; + uint64_t last_nopin; + + /* Timer used to destroy connection after requesting logout if + * initiator does not send logout request. + */ + struct spdk_poller *logout_request_timer; + + /* Timer used to destroy connection after logout if initiator does + * not close the connection. + */ + struct spdk_poller *logout_timer; + + /* Timer used to wait for connection to close + */ + struct spdk_poller *shutdown_timer; + + struct spdk_iscsi_pdu *pdu_in_progress; + enum iscsi_pdu_recv_state pdu_recv_state; + + TAILQ_HEAD(, spdk_iscsi_pdu) write_pdu_list; + TAILQ_HEAD(, spdk_iscsi_pdu) snack_pdu_list; + + int pending_r2t; + + uint16_t cid; + + /* IP address */ + char initiator_addr[MAX_INITIATOR_ADDR]; + char target_addr[MAX_TARGET_ADDR]; + + /* Initiator/Target port binds */ + char initiator_name[MAX_INITIATOR_NAME]; + struct spdk_scsi_port *initiator_port; + char target_short_name[MAX_TARGET_NAME]; + struct spdk_scsi_port *target_port; + struct spdk_iscsi_tgt_node *target; + struct spdk_scsi_dev *dev; + + /* for fast access */ + int header_digest; + int data_digest; + int full_feature; + + struct iscsi_param *params; + bool sess_param_state_negotiated[MAX_SESSION_PARAMS]; + bool conn_param_state_negotiated[MAX_CONNECTION_PARAMS]; + struct iscsi_chap_auth auth; + bool authenticated; + bool disable_chap; + bool require_chap; + bool mutual_chap; + int32_t chap_group; + uint32_t pending_task_cnt; + uint32_t data_out_cnt; + uint32_t data_in_cnt; + + uint64_t timeout; + uint64_t nopininterval; + bool nop_outstanding; + + /* + * This is the maximum data segment length that iscsi target can send + * to the initiator on this connection. Not to be confused with the + * maximum data segment length that initiators can send to iscsi target, which + * is statically defined as SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH. + */ + int MaxRecvDataSegmentLength; + + uint32_t StatSN; + uint32_t exp_statsn; + uint32_t ttt; /* target transfer tag */ + char *partial_text_parameter; + + STAILQ_ENTRY(spdk_iscsi_conn) pg_link; + bool is_stopped; /* Set true when connection is stopped for migration */ + TAILQ_HEAD(queued_r2t_tasks, spdk_iscsi_task) queued_r2t_tasks; + TAILQ_HEAD(active_r2t_tasks, spdk_iscsi_task) active_r2t_tasks; + TAILQ_HEAD(queued_datain_tasks, spdk_iscsi_task) queued_datain_tasks; + + struct spdk_iscsi_lun *luns[SPDK_SCSI_DEV_MAX_LUN]; + + TAILQ_ENTRY(spdk_iscsi_conn) conn_link; +}; + +extern struct spdk_iscsi_conn *g_conns_array; + +void iscsi_task_cpl(struct spdk_scsi_task *scsi_task); +void iscsi_task_mgmt_cpl(struct spdk_scsi_task *scsi_task); + +int initialize_iscsi_conns(void); +void shutdown_iscsi_conns(void); +void iscsi_conns_request_logout(struct spdk_iscsi_tgt_node *target); +int iscsi_get_active_conns(struct spdk_iscsi_tgt_node *target); + +int iscsi_conn_construct(struct spdk_iscsi_portal *portal, struct spdk_sock *sock); +void iscsi_conn_destruct(struct spdk_iscsi_conn *conn); +void iscsi_conn_handle_nop(struct spdk_iscsi_conn *conn); +void iscsi_conn_schedule(struct spdk_iscsi_conn *conn); +void iscsi_conn_logout(struct spdk_iscsi_conn *conn); +int iscsi_drop_conns(struct spdk_iscsi_conn *conn, + const char *conn_match, int drop_all); +int iscsi_conn_handle_queued_datain_tasks(struct spdk_iscsi_conn *conn); +int iscsi_conn_abort_queued_datain_task(struct spdk_iscsi_conn *conn, + uint32_t ref_task_tag); +int iscsi_conn_abort_queued_datain_tasks(struct spdk_iscsi_conn *conn, + struct spdk_scsi_lun *lun, + struct spdk_iscsi_pdu *pdu); + +int iscsi_conn_read_data(struct spdk_iscsi_conn *conn, int len, void *buf); +int iscsi_conn_readv_data(struct spdk_iscsi_conn *conn, + struct iovec *iov, int iovcnt); +void iscsi_conn_write_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu, + iscsi_conn_xfer_complete_cb cb_fn, + void *cb_arg); + +void iscsi_conn_free_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu); + +void iscsi_conn_info_json(struct spdk_json_write_ctx *w, struct spdk_iscsi_conn *conn); +void iscsi_conn_pdu_generic_complete(void *cb_arg); +#endif /* SPDK_ISCSI_CONN_H */ diff --git a/src/spdk/lib/iscsi/init_grp.c b/src/spdk/lib/iscsi/init_grp.c new file mode 100644 index 000000000..49e78d89d --- /dev/null +++ b/src/spdk/lib/iscsi/init_grp.c @@ -0,0 +1,787 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include "spdk/conf.h" +#include "spdk/string.h" + +#include "spdk_internal/log.h" + +#include "iscsi/iscsi.h" +#include "iscsi/init_grp.h" + +static struct spdk_iscsi_init_grp * +iscsi_init_grp_create(int tag) +{ + struct spdk_iscsi_init_grp *ig; + + ig = calloc(1, sizeof(*ig)); + if (ig == NULL) { + SPDK_ERRLOG("calloc() failed for initiator group\n"); + return NULL; + } + + ig->tag = tag; + TAILQ_INIT(&ig->initiator_head); + TAILQ_INIT(&ig->netmask_head); + return ig; +} + +static struct spdk_iscsi_initiator_name * +iscsi_init_grp_find_initiator(struct spdk_iscsi_init_grp *ig, char *name) +{ + struct spdk_iscsi_initiator_name *iname; + + TAILQ_FOREACH(iname, &ig->initiator_head, tailq) { + if (!strcmp(iname->name, name)) { + return iname; + } + } + return NULL; +} + +static int +iscsi_init_grp_add_initiator(struct spdk_iscsi_init_grp *ig, char *name) +{ + struct spdk_iscsi_initiator_name *iname; + char *p; + size_t len; + + if (ig->ninitiators >= MAX_INITIATOR) { + SPDK_ERRLOG("> MAX_INITIATOR(=%d) is not allowed\n", MAX_INITIATOR); + return -EPERM; + } + + len = strlen(name); + if (len > MAX_INITIATOR_NAME) { + SPDK_ERRLOG("Initiator Name is larger than 223 bytes\n"); + return -EINVAL; + } + + iname = iscsi_init_grp_find_initiator(ig, name); + if (iname != NULL) { + return -EEXIST; + } + + iname = calloc(1, sizeof(*iname)); + if (iname == NULL) { + SPDK_ERRLOG("malloc() failed for initiator name str\n"); + return -ENOMEM; + } + + memcpy(iname->name, name, len); + + /* Replace "ALL" by "ANY" if set */ + p = strstr(iname->name, "ALL"); + if (p != NULL) { + SPDK_WARNLOG("Please use \"%s\" instead of \"%s\"\n", "ANY", "ALL"); + SPDK_WARNLOG("Converting \"%s\" to \"%s\" automatically\n", "ALL", "ANY"); + memcpy(p, "ANY", 3); + } + + TAILQ_INSERT_TAIL(&ig->initiator_head, iname, tailq); + ig->ninitiators++; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "InitiatorName %s\n", name); + return 0; +} + +static int +iscsi_init_grp_delete_initiator(struct spdk_iscsi_init_grp *ig, char *name) +{ + struct spdk_iscsi_initiator_name *iname; + + iname = iscsi_init_grp_find_initiator(ig, name); + if (iname == NULL) { + return -ENOENT; + } + + TAILQ_REMOVE(&ig->initiator_head, iname, tailq); + ig->ninitiators--; + free(iname); + return 0; +} + +static int +iscsi_init_grp_add_initiators(struct spdk_iscsi_init_grp *ig, int num_inames, + char **inames) +{ + int i; + int rc; + + for (i = 0; i < num_inames; i++) { + rc = iscsi_init_grp_add_initiator(ig, inames[i]); + if (rc < 0) { + goto cleanup; + } + } + return 0; + +cleanup: + for (; i > 0; --i) { + iscsi_init_grp_delete_initiator(ig, inames[i - 1]); + } + return rc; +} + +static void +iscsi_init_grp_delete_all_initiators(struct spdk_iscsi_init_grp *ig) +{ + struct spdk_iscsi_initiator_name *iname, *tmp; + + TAILQ_FOREACH_SAFE(iname, &ig->initiator_head, tailq, tmp) { + TAILQ_REMOVE(&ig->initiator_head, iname, tailq); + ig->ninitiators--; + free(iname); + } +} + +static int +iscsi_init_grp_delete_initiators(struct spdk_iscsi_init_grp *ig, int num_inames, char **inames) +{ + int i; + int rc; + + for (i = 0; i < num_inames; i++) { + rc = iscsi_init_grp_delete_initiator(ig, inames[i]); + if (rc < 0) { + goto cleanup; + } + } + return 0; + +cleanup: + for (; i > 0; --i) { + rc = iscsi_init_grp_add_initiator(ig, inames[i - 1]); + if (rc != 0) { + iscsi_init_grp_delete_all_initiators(ig); + break; + } + } + return -1; +} + +static struct spdk_iscsi_initiator_netmask * +iscsi_init_grp_find_netmask(struct spdk_iscsi_init_grp *ig, const char *mask) +{ + struct spdk_iscsi_initiator_netmask *netmask; + + TAILQ_FOREACH(netmask, &ig->netmask_head, tailq) { + if (!strcmp(netmask->mask, mask)) { + return netmask; + } + } + return NULL; +} + +static int +iscsi_init_grp_add_netmask(struct spdk_iscsi_init_grp *ig, char *mask) +{ + struct spdk_iscsi_initiator_netmask *imask; + char *p; + size_t len; + + if (ig->nnetmasks >= MAX_NETMASK) { + SPDK_ERRLOG("> MAX_NETMASK(=%d) is not allowed\n", MAX_NETMASK); + return -EPERM; + } + + len = strlen(mask); + if (len > MAX_INITIATOR_ADDR) { + SPDK_ERRLOG("Initiator Name is larger than %d bytes\n", MAX_INITIATOR_ADDR); + return -EINVAL; + } + + imask = iscsi_init_grp_find_netmask(ig, mask); + if (imask != NULL) { + return -EEXIST; + } + + imask = calloc(1, sizeof(*imask)); + if (imask == NULL) { + SPDK_ERRLOG("malloc() failed for inititator mask str\n"); + return -ENOMEM; + } + + memcpy(imask->mask, mask, len); + + /* Replace "ALL" by "ANY" if set */ + p = strstr(imask->mask, "ALL"); + if (p != NULL) { + SPDK_WARNLOG("Please use \"%s\" instead of \"%s\"\n", "ANY", "ALL"); + SPDK_WARNLOG("Converting \"%s\" to \"%s\" automatically\n", "ALL", "ANY"); + memcpy(p, "ANY", 3); + } + + TAILQ_INSERT_TAIL(&ig->netmask_head, imask, tailq); + ig->nnetmasks++; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Netmask %s\n", mask); + return 0; +} + +static int +iscsi_init_grp_delete_netmask(struct spdk_iscsi_init_grp *ig, char *mask) +{ + struct spdk_iscsi_initiator_netmask *imask; + + imask = iscsi_init_grp_find_netmask(ig, mask); + if (imask == NULL) { + return -ENOENT; + } + + TAILQ_REMOVE(&ig->netmask_head, imask, tailq); + ig->nnetmasks--; + free(imask); + return 0; +} + +static int +iscsi_init_grp_add_netmasks(struct spdk_iscsi_init_grp *ig, int num_imasks, char **imasks) +{ + int i; + int rc; + + for (i = 0; i < num_imasks; i++) { + rc = iscsi_init_grp_add_netmask(ig, imasks[i]); + if (rc != 0) { + goto cleanup; + } + } + return 0; + +cleanup: + for (; i > 0; --i) { + iscsi_init_grp_delete_netmask(ig, imasks[i - 1]); + } + return rc; +} + +static void +iscsi_init_grp_delete_all_netmasks(struct spdk_iscsi_init_grp *ig) +{ + struct spdk_iscsi_initiator_netmask *imask, *tmp; + + TAILQ_FOREACH_SAFE(imask, &ig->netmask_head, tailq, tmp) { + TAILQ_REMOVE(&ig->netmask_head, imask, tailq); + ig->nnetmasks--; + free(imask); + } +} + +static int +iscsi_init_grp_delete_netmasks(struct spdk_iscsi_init_grp *ig, int num_imasks, char **imasks) +{ + int i; + int rc; + + for (i = 0; i < num_imasks; i++) { + rc = iscsi_init_grp_delete_netmask(ig, imasks[i]); + if (rc != 0) { + goto cleanup; + } + } + return 0; + +cleanup: + for (; i > 0; --i) { + rc = iscsi_init_grp_add_netmask(ig, imasks[i - 1]); + if (rc != 0) { + iscsi_init_grp_delete_all_netmasks(ig); + break; + } + } + return -1; +} + +/* Read spdk iscsi target's config file and create initiator group */ +static int +iscsi_parse_init_grp(struct spdk_conf_section *sp) +{ + int i, rc = 0; + const char *val = NULL; + int num_initiator_names; + int num_initiator_masks; + char **initiators = NULL, **netmasks = NULL; + int tag = spdk_conf_section_get_num(sp); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "add initiator group %d\n", tag); + + val = spdk_conf_section_get_val(sp, "Comment"); + if (val != NULL) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Comment %s\n", val); + } + + /* counts number of definitions */ + for (i = 0; ; i++) { + val = spdk_conf_section_get_nval(sp, "InitiatorName", i); + if (val == NULL) { + break; + } + } + if (i == 0) { + SPDK_ERRLOG("num_initiator_names = 0\n"); + return -EINVAL; + } + num_initiator_names = i; + if (num_initiator_names > MAX_INITIATOR) { + SPDK_ERRLOG("%d > MAX_INITIATOR\n", num_initiator_names); + return -E2BIG; + } + for (i = 0; ; i++) { + val = spdk_conf_section_get_nval(sp, "Netmask", i); + if (val == NULL) { + break; + } + } + if (i == 0) { + SPDK_ERRLOG("num_initiator_mask = 0\n"); + return -EINVAL; + } + num_initiator_masks = i; + if (num_initiator_masks > MAX_NETMASK) { + SPDK_ERRLOG("%d > MAX_NETMASK\n", num_initiator_masks); + return -E2BIG; + } + + initiators = calloc(num_initiator_names, sizeof(char *)); + if (!initiators) { + SPDK_ERRLOG("calloc() failed for temp initiator name array\n"); + return -ENOMEM; + } + for (i = 0; i < num_initiator_names; i++) { + val = spdk_conf_section_get_nval(sp, "InitiatorName", i); + if (!val) { + SPDK_ERRLOG("InitiatorName %d not found\n", i); + rc = -EINVAL; + goto cleanup; + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "InitiatorName %s\n", val); + initiators[i] = strdup(val); + if (!initiators[i]) { + SPDK_ERRLOG("strdup() failed for temp initiator name\n"); + rc = -ENOMEM; + goto cleanup; + } + } + netmasks = calloc(num_initiator_masks, sizeof(char *)); + if (!netmasks) { + SPDK_ERRLOG("malloc() failed for portal group\n"); + rc = -ENOMEM; + goto cleanup; + } + for (i = 0; i < num_initiator_masks; i++) { + val = spdk_conf_section_get_nval(sp, "Netmask", i); + if (!val) { + SPDK_ERRLOG("Netmask %d not found\n", i); + rc = -EINVAL; + goto cleanup; + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Netmask %s\n", val); + netmasks[i] = strdup(val); + if (!netmasks[i]) { + SPDK_ERRLOG("strdup() failed for temp initiator mask\n"); + rc = -ENOMEM; + goto cleanup; + } + } + + rc = iscsi_init_grp_create_from_initiator_list(tag, + num_initiator_names, initiators, num_initiator_masks, netmasks); + +cleanup: + if (initiators) { + for (i = 0; i < num_initiator_names; i++) { + if (initiators[i]) { + free(initiators[i]); + } + } + free(initiators); + } + if (netmasks) { + for (i = 0; i < num_initiator_masks; i++) { + if (netmasks[i]) { + free(netmasks[i]); + } + } + free(netmasks); + } + return rc; +} + +int +iscsi_init_grp_register(struct spdk_iscsi_init_grp *ig) +{ + struct spdk_iscsi_init_grp *tmp; + int rc = -1; + + assert(ig != NULL); + + pthread_mutex_lock(&g_iscsi.mutex); + tmp = iscsi_init_grp_find_by_tag(ig->tag); + if (tmp == NULL) { + TAILQ_INSERT_TAIL(&g_iscsi.ig_head, ig, tailq); + rc = 0; + } + pthread_mutex_unlock(&g_iscsi.mutex); + + return rc; +} + +/* + * Create initiator group from list of initiator ip/hostnames and netmasks + * The initiator hostname/netmask lists are allocated by the caller on the + * heap. Freed later by common initiator_group_destroy() code + */ +int +iscsi_init_grp_create_from_initiator_list(int tag, + int num_initiator_names, + char **initiator_names, + int num_initiator_masks, + char **initiator_masks) +{ + int rc = -1; + struct spdk_iscsi_init_grp *ig = NULL; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "add initiator group (from initiator list) tag=%d, #initiators=%d, #masks=%d\n", + tag, num_initiator_names, num_initiator_masks); + + ig = iscsi_init_grp_create(tag); + if (!ig) { + SPDK_ERRLOG("initiator group create error (%d)\n", tag); + return rc; + } + + rc = iscsi_init_grp_add_initiators(ig, num_initiator_names, + initiator_names); + if (rc < 0) { + SPDK_ERRLOG("add initiator name error\n"); + goto cleanup; + } + + rc = iscsi_init_grp_add_netmasks(ig, num_initiator_masks, + initiator_masks); + if (rc < 0) { + SPDK_ERRLOG("add initiator netmask error\n"); + goto cleanup; + } + + rc = iscsi_init_grp_register(ig); + if (rc < 0) { + SPDK_ERRLOG("initiator group register error (%d)\n", tag); + goto cleanup; + } + return 0; + +cleanup: + iscsi_init_grp_destroy(ig); + return rc; +} + +int +iscsi_init_grp_add_initiators_from_initiator_list(int tag, + int num_initiator_names, + char **initiator_names, + int num_initiator_masks, + char **initiator_masks) +{ + int rc = -1; + struct spdk_iscsi_init_grp *ig; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "add initiator to initiator group: tag=%d, #initiators=%d, #masks=%d\n", + tag, num_initiator_names, num_initiator_masks); + + pthread_mutex_lock(&g_iscsi.mutex); + ig = iscsi_init_grp_find_by_tag(tag); + if (!ig) { + pthread_mutex_unlock(&g_iscsi.mutex); + SPDK_ERRLOG("initiator group (%d) is not found\n", tag); + return rc; + } + + rc = iscsi_init_grp_add_initiators(ig, num_initiator_names, + initiator_names); + if (rc < 0) { + SPDK_ERRLOG("add initiator name error\n"); + goto error; + } + + rc = iscsi_init_grp_add_netmasks(ig, num_initiator_masks, + initiator_masks); + if (rc < 0) { + SPDK_ERRLOG("add initiator netmask error\n"); + iscsi_init_grp_delete_initiators(ig, num_initiator_names, + initiator_names); + } + +error: + pthread_mutex_unlock(&g_iscsi.mutex); + return rc; +} + +int +iscsi_init_grp_delete_initiators_from_initiator_list(int tag, + int num_initiator_names, + char **initiator_names, + int num_initiator_masks, + char **initiator_masks) +{ + int rc = -1; + struct spdk_iscsi_init_grp *ig; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "delete initiator from initiator group: tag=%d, #initiators=%d, #masks=%d\n", + tag, num_initiator_names, num_initiator_masks); + + pthread_mutex_lock(&g_iscsi.mutex); + ig = iscsi_init_grp_find_by_tag(tag); + if (!ig) { + pthread_mutex_unlock(&g_iscsi.mutex); + SPDK_ERRLOG("initiator group (%d) is not found\n", tag); + return rc; + } + + rc = iscsi_init_grp_delete_initiators(ig, num_initiator_names, + initiator_names); + if (rc < 0) { + SPDK_ERRLOG("delete initiator name error\n"); + goto error; + } + + rc = iscsi_init_grp_delete_netmasks(ig, num_initiator_masks, + initiator_masks); + if (rc < 0) { + SPDK_ERRLOG("delete initiator netmask error\n"); + iscsi_init_grp_add_initiators(ig, num_initiator_names, + initiator_names); + goto error; + } + +error: + pthread_mutex_unlock(&g_iscsi.mutex); + return rc; +} + +void +iscsi_init_grp_destroy(struct spdk_iscsi_init_grp *ig) +{ + if (!ig) { + return; + } + + iscsi_init_grp_delete_all_initiators(ig); + iscsi_init_grp_delete_all_netmasks(ig); + free(ig); +}; + +struct spdk_iscsi_init_grp * +iscsi_init_grp_find_by_tag(int tag) +{ + struct spdk_iscsi_init_grp *ig; + + TAILQ_FOREACH(ig, &g_iscsi.ig_head, tailq) { + if (ig->tag == tag) { + return ig; + } + } + + return NULL; +} + +int +iscsi_parse_init_grps(void) +{ + struct spdk_conf_section *sp; + int rc; + + sp = spdk_conf_first_section(NULL); + while (sp != NULL) { + if (spdk_conf_section_match_prefix(sp, "InitiatorGroup")) { + if (spdk_conf_section_get_num(sp) == 0) { + SPDK_ERRLOG("Group 0 is invalid\n"); + return -1; + } + rc = iscsi_parse_init_grp(sp); + if (rc < 0) { + SPDK_ERRLOG("parse_init_group() failed\n"); + return -1; + } + } + sp = spdk_conf_next_section(sp); + } + return 0; +} + +void +iscsi_init_grps_destroy(void) +{ + struct spdk_iscsi_init_grp *ig, *tmp; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "iscsi_init_grp_array_destroy\n"); + pthread_mutex_lock(&g_iscsi.mutex); + TAILQ_FOREACH_SAFE(ig, &g_iscsi.ig_head, tailq, tmp) { + TAILQ_REMOVE(&g_iscsi.ig_head, ig, tailq); + iscsi_init_grp_destroy(ig); + } + pthread_mutex_unlock(&g_iscsi.mutex); +} + +struct spdk_iscsi_init_grp * +iscsi_init_grp_unregister(int tag) +{ + struct spdk_iscsi_init_grp *ig; + + pthread_mutex_lock(&g_iscsi.mutex); + TAILQ_FOREACH(ig, &g_iscsi.ig_head, tailq) { + if (ig->tag == tag) { + TAILQ_REMOVE(&g_iscsi.ig_head, ig, tailq); + pthread_mutex_unlock(&g_iscsi.mutex); + return ig; + } + } + pthread_mutex_unlock(&g_iscsi.mutex); + return NULL; +} + +static const char *initiator_group_section = \ + "\n" + "# Users must change the InitiatorGroup section(s) to match the IP\n" + "# addresses and initiator configuration in their environment.\n" + "# Netmask can be used to specify a single IP address or a range of IP addresses\n" + "# Netmask 192.168.1.20 <== single IP address\n" + "# Netmask 192.168.1.0/24 <== IP range 192.168.1.*\n"; + +#define INITIATOR_GROUP_TMPL \ +"[InitiatorGroup%d]\n" \ +" Comment \"Initiator Group%d\"\n" + +#define INITIATOR_TMPL \ +" InitiatorName " + +#define NETMASK_TMPL \ +" Netmask " + +void +iscsi_init_grps_config_text(FILE *fp) +{ + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_initiator_name *iname; + struct spdk_iscsi_initiator_netmask *imask; + + /* Create initiator group section */ + fprintf(fp, "%s", initiator_group_section); + + /* Dump initiator groups */ + TAILQ_FOREACH(ig, &g_iscsi.ig_head, tailq) { + if (NULL == ig) { continue; } + fprintf(fp, INITIATOR_GROUP_TMPL, ig->tag, ig->tag); + + /* Dump initiators */ + fprintf(fp, INITIATOR_TMPL); + TAILQ_FOREACH(iname, &ig->initiator_head, tailq) { + fprintf(fp, "%s ", iname->name); + } + fprintf(fp, "\n"); + + /* Dump netmasks */ + fprintf(fp, NETMASK_TMPL); + TAILQ_FOREACH(imask, &ig->netmask_head, tailq) { + fprintf(fp, "%s ", imask->mask); + } + fprintf(fp, "\n"); + } +} + +static void +iscsi_init_grp_info_json(struct spdk_iscsi_init_grp *ig, + struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_initiator_name *iname; + struct spdk_iscsi_initiator_netmask *imask; + + spdk_json_write_object_begin(w); + + spdk_json_write_named_int32(w, "tag", ig->tag); + + spdk_json_write_named_array_begin(w, "initiators"); + TAILQ_FOREACH(iname, &ig->initiator_head, tailq) { + spdk_json_write_string(w, iname->name); + } + spdk_json_write_array_end(w); + + spdk_json_write_named_array_begin(w, "netmasks"); + TAILQ_FOREACH(imask, &ig->netmask_head, tailq) { + spdk_json_write_string(w, imask->mask); + } + spdk_json_write_array_end(w); + + spdk_json_write_object_end(w); +} + +static void +iscsi_init_grp_config_json(struct spdk_iscsi_init_grp *ig, + struct spdk_json_write_ctx *w) +{ + spdk_json_write_object_begin(w); + + spdk_json_write_named_string(w, "method", "iscsi_create_initiator_group"); + + spdk_json_write_name(w, "params"); + iscsi_init_grp_info_json(ig, w); + + spdk_json_write_object_end(w); +} + +void +iscsi_init_grps_info_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_init_grp *ig; + + TAILQ_FOREACH(ig, &g_iscsi.ig_head, tailq) { + iscsi_init_grp_info_json(ig, w); + } +} + +void +iscsi_init_grps_config_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_init_grp *ig; + + TAILQ_FOREACH(ig, &g_iscsi.ig_head, tailq) { + iscsi_init_grp_config_json(ig, w); + } +} diff --git a/src/spdk/lib/iscsi/init_grp.h b/src/spdk/lib/iscsi/init_grp.h new file mode 100644 index 000000000..8913c98cd --- /dev/null +++ b/src/spdk/lib/iscsi/init_grp.h @@ -0,0 +1,81 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SPDK_INIT_GRP_H +#define SPDK_INIT_GRP_H + +#include "spdk/conf.h" +#include "iscsi/iscsi.h" +#include "iscsi/conn.h" + +struct spdk_iscsi_initiator_name { + char name[MAX_INITIATOR_NAME + 1]; + TAILQ_ENTRY(spdk_iscsi_initiator_name) tailq; +}; + +struct spdk_iscsi_initiator_netmask { + char mask[MAX_INITIATOR_ADDR + 1]; + TAILQ_ENTRY(spdk_iscsi_initiator_netmask) tailq; +}; + +struct spdk_iscsi_init_grp { + int ninitiators; + TAILQ_HEAD(, spdk_iscsi_initiator_name) initiator_head; + int nnetmasks; + TAILQ_HEAD(, spdk_iscsi_initiator_netmask) netmask_head; + int ref; + int tag; + TAILQ_ENTRY(spdk_iscsi_init_grp) tailq; +}; + +/* SPDK iSCSI Initiator Group management API */ +int iscsi_init_grp_create_from_initiator_list(int tag, + int num_initiator_names, char **initiator_names, + int num_initiator_masks, char **initiator_masks); +int iscsi_init_grp_add_initiators_from_initiator_list(int tag, + int num_initiator_names, char **initiator_names, + int num_initiator_masks, char **initiator_masks); +int iscsi_init_grp_delete_initiators_from_initiator_list(int tag, + int num_initiator_names, char **initiator_names, + int num_initiator_masks, char **initiator_masks); +int iscsi_init_grp_register(struct spdk_iscsi_init_grp *ig); +struct spdk_iscsi_init_grp *iscsi_init_grp_unregister(int tag); +struct spdk_iscsi_init_grp *iscsi_init_grp_find_by_tag(int tag); +void iscsi_init_grp_destroy(struct spdk_iscsi_init_grp *ig); +int iscsi_parse_init_grps(void); +void iscsi_init_grps_destroy(void); +void iscsi_init_grps_config_text(FILE *fp); +void iscsi_init_grps_info_json(struct spdk_json_write_ctx *w); +void iscsi_init_grps_config_json(struct spdk_json_write_ctx *w); +#endif /* SPDK_INIT_GRP_H */ diff --git a/src/spdk/lib/iscsi/iscsi.c b/src/spdk/lib/iscsi/iscsi.c new file mode 100644 index 000000000..febf4cac4 --- /dev/null +++ b/src/spdk/lib/iscsi/iscsi.c @@ -0,0 +1,4797 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include "spdk/base64.h" +#include "spdk/crc32.h" +#include "spdk/endian.h" +#include "spdk/env.h" +#include "spdk/likely.h" +#include "spdk/trace.h" +#include "spdk/sock.h" +#include "spdk/string.h" +#include "spdk/queue.h" +#include "spdk/net.h" + +#include "iscsi/md5.h" +#include "iscsi/iscsi.h" +#include "iscsi/param.h" +#include "iscsi/tgt_node.h" +#include "iscsi/task.h" +#include "iscsi/conn.h" +#include "spdk/scsi.h" +#include "spdk/bdev.h" +#include "iscsi/portal_grp.h" + +#include "spdk_internal/log.h" + +#define MAX_TMPBUF 1024 + +#define SPDK_CRC32C_INITIAL 0xffffffffUL +#define SPDK_CRC32C_XOR 0xffffffffUL + +#ifdef __FreeBSD__ +#define HAVE_SRANDOMDEV 1 +#define HAVE_ARC4RANDOM 1 +#endif + +struct spdk_iscsi_globals g_iscsi = { + .mutex = PTHREAD_MUTEX_INITIALIZER, + .portal_head = TAILQ_HEAD_INITIALIZER(g_iscsi.portal_head), + .pg_head = TAILQ_HEAD_INITIALIZER(g_iscsi.pg_head), + .ig_head = TAILQ_HEAD_INITIALIZER(g_iscsi.ig_head), + .target_head = TAILQ_HEAD_INITIALIZER(g_iscsi.target_head), + .auth_group_head = TAILQ_HEAD_INITIALIZER(g_iscsi.auth_group_head), + .poll_group_head = TAILQ_HEAD_INITIALIZER(g_iscsi.poll_group_head), +}; + +#define MATCH_DIGEST_WORD(BUF, CRC32C) \ + ( ((((uint32_t) *((uint8_t *)(BUF)+0)) << 0) \ + | (((uint32_t) *((uint8_t *)(BUF)+1)) << 8) \ + | (((uint32_t) *((uint8_t *)(BUF)+2)) << 16) \ + | (((uint32_t) *((uint8_t *)(BUF)+3)) << 24)) \ + == (CRC32C)) + +#ifndef HAVE_SRANDOMDEV +static void +srandomdev(void) +{ + unsigned long seed; + time_t now; + pid_t pid; + + pid = getpid(); + now = time(NULL); + seed = pid ^ now; + srandom(seed); +} +#endif /* HAVE_SRANDOMDEV */ + +#ifndef HAVE_ARC4RANDOM +static int g_arc4random_initialized = 0; + +static uint32_t +arc4random(void) +{ + uint32_t r; + uint32_t r1, r2; + + if (!g_arc4random_initialized) { + srandomdev(); + g_arc4random_initialized = 1; + } + r1 = (uint32_t)(random() & 0xffff); + r2 = (uint32_t)(random() & 0xffff); + r = (r1 << 16) | r2; + return r; +} +#endif /* HAVE_ARC4RANDOM */ + +static void +gen_random(uint8_t *buf, size_t len) +{ + uint32_t r; + size_t idx; + + for (idx = 0; idx < len; idx++) { + r = arc4random(); + buf[idx] = (uint8_t) r; + } +} + +static uint64_t +iscsi_get_isid(const uint8_t isid[6]) +{ + return (uint64_t)isid[0] << 40 | + (uint64_t)isid[1] << 32 | + (uint64_t)isid[2] << 24 | + (uint64_t)isid[3] << 16 | + (uint64_t)isid[4] << 8 | + (uint64_t)isid[5]; +} + +static int +bin2hex(char *buf, size_t len, const uint8_t *data, size_t data_len) +{ + const char *digits = "0123456789ABCDEF"; + size_t total = 0; + size_t idx; + + if (len < 3) { + return -1; + } + buf[total] = '0'; + total++; + buf[total] = 'x'; + total++; + buf[total] = '\0'; + + for (idx = 0; idx < data_len; idx++) { + if (total + 3 > len) { + buf[total] = '\0'; + return - 1; + } + buf[total] = digits[(data[idx] >> 4) & 0x0fU]; + total++; + buf[total] = digits[data[idx] & 0x0fU]; + total++; + } + buf[total] = '\0'; + return total; +} + +static int +hex2bin(uint8_t *data, size_t data_len, const char *str) +{ + const char *digits = "0123456789ABCDEF"; + const char *dp; + const char *p; + size_t total = 0; + int n0, n1; + + p = str; + if (p[0] != '0' && (p[1] != 'x' && p[1] != 'X')) { + return -1; + } + p += 2; + + while (p[0] != '\0' && p[1] != '\0') { + if (total >= data_len) { + return -1; + } + dp = strchr(digits, toupper((int) p[0])); + if (dp == NULL) { + return -1; + } + n0 = (int)(dp - digits); + dp = strchr(digits, toupper((int) p[1])); + if (dp == NULL) { + return -1; + } + n1 = (int)(dp - digits); + + data[total] = (uint8_t)(((n0 & 0x0fU) << 4) | (n1 & 0x0fU)); + total++; + p += 2; + } + return total; +} + +static int +iscsi_reject(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu, + int reason) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_reject *rsph; + uint8_t *data; + int total_ahs_len; + int data_len; + int alloc_len; + + pdu->is_rejected = true; + + total_ahs_len = pdu->bhs.total_ahs_len; + data_len = 0; + alloc_len = ISCSI_BHS_LEN + (4 * total_ahs_len); + + if (conn->header_digest) { + alloc_len += ISCSI_DIGEST_LEN; + } + + data = calloc(1, alloc_len); + if (!data) { + SPDK_ERRLOG("calloc() failed for data segment\n"); + return -ENOMEM; + } + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Reject PDU reason=%d\n", reason); + + if (conn->sess != NULL) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "StatSN=%u, ExpCmdSN=%u, MaxCmdSN=%u\n", + conn->StatSN, conn->sess->ExpCmdSN, + conn->sess->MaxCmdSN); + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "StatSN=%u\n", conn->StatSN); + } + + memcpy(data, &pdu->bhs, ISCSI_BHS_LEN); + data_len += ISCSI_BHS_LEN; + + if (total_ahs_len != 0) { + total_ahs_len = spdk_min((4 * total_ahs_len), ISCSI_AHS_LEN); + memcpy(data + data_len, pdu->ahs, total_ahs_len); + data_len += total_ahs_len; + } + + if (conn->header_digest) { + memcpy(data + data_len, pdu->header_digest, ISCSI_DIGEST_LEN); + data_len += ISCSI_DIGEST_LEN; + } + + rsp_pdu = iscsi_get_pdu(conn); + if (rsp_pdu == NULL) { + free(data); + return -ENOMEM; + } + + rsph = (struct iscsi_bhs_reject *)&rsp_pdu->bhs; + rsp_pdu->data = data; + rsph->opcode = ISCSI_OP_REJECT; + rsph->flags |= 0x80; /* bit 0 is default to 1 */ + rsph->reason = reason; + DSET24(rsph->data_segment_len, data_len); + + rsph->ffffffff = 0xffffffffU; + to_be32(&rsph->stat_sn, conn->StatSN); + conn->StatSN++; + + if (conn->sess != NULL) { + to_be32(&rsph->exp_cmd_sn, conn->sess->ExpCmdSN); + to_be32(&rsph->max_cmd_sn, conn->sess->MaxCmdSN); + } else { + to_be32(&rsph->exp_cmd_sn, 1); + to_be32(&rsph->max_cmd_sn, 1); + } + + SPDK_LOGDUMP(SPDK_LOG_ISCSI, "PDU", (void *)&rsp_pdu->bhs, ISCSI_BHS_LEN); + + iscsi_conn_write_pdu(conn, rsp_pdu, iscsi_conn_pdu_generic_complete, NULL); + + return 0; +} + +uint32_t +iscsi_pdu_calc_header_digest(struct spdk_iscsi_pdu *pdu) +{ + uint32_t crc32c; + uint32_t ahs_len_bytes = pdu->bhs.total_ahs_len * 4; + + crc32c = SPDK_CRC32C_INITIAL; + crc32c = spdk_crc32c_update(&pdu->bhs, ISCSI_BHS_LEN, crc32c); + + if (ahs_len_bytes) { + crc32c = spdk_crc32c_update(pdu->ahs, ahs_len_bytes, crc32c); + } + + /* BHS and AHS are always 4-byte multiples in length, so no padding is necessary. */ + crc32c = crc32c ^ SPDK_CRC32C_XOR; + return crc32c; +} + +uint32_t +iscsi_pdu_calc_data_digest(struct spdk_iscsi_pdu *pdu) +{ + uint32_t data_len = DGET24(pdu->bhs.data_segment_len); + uint32_t crc32c; + uint32_t mod; + struct iovec iov; + uint32_t num_blocks; + + crc32c = SPDK_CRC32C_INITIAL; + if (spdk_likely(!pdu->dif_insert_or_strip)) { + crc32c = spdk_crc32c_update(pdu->data, data_len, crc32c); + } else { + iov.iov_base = pdu->data_buf; + iov.iov_len = pdu->data_buf_len; + num_blocks = pdu->data_buf_len / pdu->dif_ctx.block_size; + + spdk_dif_update_crc32c(&iov, 1, num_blocks, &crc32c, &pdu->dif_ctx); + } + + mod = data_len % ISCSI_ALIGNMENT; + if (mod != 0) { + uint32_t pad_length = ISCSI_ALIGNMENT - mod; + uint8_t pad[3] = {0, 0, 0}; + + assert(pad_length > 0); + assert(pad_length <= sizeof(pad)); + crc32c = spdk_crc32c_update(pad, pad_length, crc32c); + } + + crc32c = crc32c ^ SPDK_CRC32C_XOR; + return crc32c; +} + +static int +iscsi_conn_read_data_segment(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *pdu, + uint32_t segment_len) +{ + struct iovec buf_iov, iovs[32]; + int rc, _rc; + + if (spdk_likely(!pdu->dif_insert_or_strip)) { + return iscsi_conn_read_data(conn, + segment_len - pdu->data_valid_bytes, + pdu->data_buf + pdu->data_valid_bytes); + } else { + buf_iov.iov_base = pdu->data_buf; + buf_iov.iov_len = pdu->data_buf_len; + rc = spdk_dif_set_md_interleave_iovs(iovs, 32, &buf_iov, 1, + pdu->data_valid_bytes, + segment_len - pdu->data_valid_bytes, NULL, + &pdu->dif_ctx); + if (rc > 0) { + rc = iscsi_conn_readv_data(conn, iovs, rc); + if (rc > 0) { + _rc = spdk_dif_generate_stream(&buf_iov, 1, + pdu->data_valid_bytes, rc, + &pdu->dif_ctx); + if (_rc != 0) { + SPDK_ERRLOG("DIF generate failed\n"); + rc = _rc; + } + } + } else { + SPDK_ERRLOG("Setup iovs for interleaved metadata failed\n"); + } + return rc; + } +} + +struct _iscsi_sgl { + struct iovec *iov; + int iovcnt; + uint32_t iov_offset; + uint32_t total_size; +}; + +static inline void +_iscsi_sgl_init(struct _iscsi_sgl *s, struct iovec *iovs, int iovcnt, + uint32_t iov_offset) +{ + s->iov = iovs; + s->iovcnt = iovcnt; + s->iov_offset = iov_offset; + s->total_size = 0; +} + +static inline bool +_iscsi_sgl_append(struct _iscsi_sgl *s, uint8_t *data, uint32_t data_len) +{ + if (s->iov_offset >= data_len) { + s->iov_offset -= data_len; + } else { + assert(s->iovcnt > 0); + s->iov->iov_base = data + s->iov_offset; + s->iov->iov_len = data_len - s->iov_offset; + s->total_size += data_len - s->iov_offset; + s->iov_offset = 0; + s->iov++; + s->iovcnt--; + if (s->iovcnt == 0) { + return false; + } + } + + return true; +} + +/* Build iovec array to leave metadata space for every data block + * when reading data segment from socket. + */ +static inline bool +_iscsi_sgl_append_with_md(struct _iscsi_sgl *s, + void *buf, uint32_t buf_len, uint32_t data_len, + struct spdk_dif_ctx *dif_ctx) +{ + int rc; + uint32_t total_size = 0; + struct iovec buf_iov; + + if (s->iov_offset >= data_len) { + s->iov_offset -= data_len; + } else { + buf_iov.iov_base = buf; + buf_iov.iov_len = buf_len; + rc = spdk_dif_set_md_interleave_iovs(s->iov, s->iovcnt, &buf_iov, 1, + s->iov_offset, data_len - s->iov_offset, + &total_size, dif_ctx); + if (rc < 0) { + SPDK_ERRLOG("Failed to setup iovs for DIF strip\n"); + return false; + } + + s->total_size += total_size; + s->iov_offset = 0; + assert(s->iovcnt >= rc); + s->iovcnt -= rc; + s->iov += rc; + + if (s->iovcnt == 0) { + return false; + } + } + + return true; +} + +int +iscsi_build_iovs(struct spdk_iscsi_conn *conn, struct iovec *iovs, int iovcnt, + struct spdk_iscsi_pdu *pdu, uint32_t *_mapped_length) +{ + struct _iscsi_sgl sgl; + int enable_digest; + uint32_t total_ahs_len; + uint32_t data_len; + + if (iovcnt == 0) { + return 0; + } + + total_ahs_len = pdu->bhs.total_ahs_len; + data_len = DGET24(pdu->bhs.data_segment_len); + data_len = ISCSI_ALIGN(data_len); + + enable_digest = 1; + if (pdu->bhs.opcode == ISCSI_OP_LOGIN_RSP) { + /* this PDU should be sent without digest */ + enable_digest = 0; + } + + _iscsi_sgl_init(&sgl, iovs, iovcnt, pdu->writev_offset); + + /* BHS */ + if (!_iscsi_sgl_append(&sgl, (uint8_t *)&pdu->bhs, ISCSI_BHS_LEN)) { + goto end; + } + /* AHS */ + if (total_ahs_len > 0) { + if (!_iscsi_sgl_append(&sgl, pdu->ahs, 4 * total_ahs_len)) { + goto end; + } + } + + /* Header Digest */ + if (enable_digest && conn->header_digest) { + if (!_iscsi_sgl_append(&sgl, pdu->header_digest, ISCSI_DIGEST_LEN)) { + goto end; + } + } + + /* Data Segment */ + if (data_len > 0) { + if (!pdu->dif_insert_or_strip) { + if (!_iscsi_sgl_append(&sgl, pdu->data, data_len)) { + goto end; + } + } else { + if (!_iscsi_sgl_append_with_md(&sgl, pdu->data, pdu->data_buf_len, + data_len, &pdu->dif_ctx)) { + goto end; + } + } + } + + /* Data Digest */ + if (enable_digest && conn->data_digest && data_len != 0) { + _iscsi_sgl_append(&sgl, pdu->data_digest, ISCSI_DIGEST_LEN); + } + +end: + if (_mapped_length != NULL) { + *_mapped_length = sgl.total_size; + } + + return iovcnt - sgl.iovcnt; +} + +void iscsi_free_sess(struct spdk_iscsi_sess *sess) +{ + if (sess == NULL) { + return; + } + + sess->tag = 0; + sess->target = NULL; + sess->session_type = SESSION_TYPE_INVALID; + iscsi_param_free(sess->params); + free(sess->conns); + spdk_scsi_port_free(&sess->initiator_port); + spdk_mempool_put(g_iscsi.session_pool, (void *)sess); +} + +static int +create_iscsi_sess(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_tgt_node *target, + enum session_type session_type) +{ + struct spdk_iscsi_sess *sess; + int rc; + + sess = spdk_mempool_get(g_iscsi.session_pool); + if (!sess) { + SPDK_ERRLOG("Unable to get session object\n"); + SPDK_ERRLOG("MaxSessions set to %d\n", g_iscsi.MaxSessions); + return -ENOMEM; + } + + /* configuration values */ + pthread_mutex_lock(&g_iscsi.mutex); + + sess->MaxConnections = g_iscsi.MaxConnectionsPerSession; + sess->MaxOutstandingR2T = DEFAULT_MAXOUTSTANDINGR2T; + + sess->DefaultTime2Wait = g_iscsi.DefaultTime2Wait; + sess->DefaultTime2Retain = g_iscsi.DefaultTime2Retain; + sess->FirstBurstLength = g_iscsi.FirstBurstLength; + sess->MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH; + sess->InitialR2T = DEFAULT_INITIALR2T; + sess->ImmediateData = g_iscsi.ImmediateData; + sess->DataPDUInOrder = DEFAULT_DATAPDUINORDER; + sess->DataSequenceInOrder = DEFAULT_DATASEQUENCEINORDER; + sess->ErrorRecoveryLevel = g_iscsi.ErrorRecoveryLevel; + + pthread_mutex_unlock(&g_iscsi.mutex); + + sess->tag = conn->pg_tag; + + sess->conns = calloc(sess->MaxConnections, sizeof(*sess->conns)); + if (!sess->conns) { + SPDK_ERRLOG("calloc() failed for connection array\n"); + return -ENOMEM; + } + + sess->connections = 0; + + sess->conns[sess->connections] = conn; + sess->connections++; + + sess->params = NULL; + sess->target = target; + sess->isid = 0; + sess->session_type = session_type; + sess->current_text_itt = 0xffffffffU; + + /* set default params */ + rc = iscsi_sess_params_init(&sess->params); + if (rc < 0) { + SPDK_ERRLOG("iscsi_sess_params_init() failed\n"); + goto error_return; + } + /* replace with config value */ + rc = iscsi_param_set_int(sess->params, "MaxConnections", + sess->MaxConnections); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + rc = iscsi_param_set_int(sess->params, "MaxOutstandingR2T", + sess->MaxOutstandingR2T); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + rc = iscsi_param_set_int(sess->params, "DefaultTime2Wait", + sess->DefaultTime2Wait); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + rc = iscsi_param_set_int(sess->params, "DefaultTime2Retain", + sess->DefaultTime2Retain); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + rc = iscsi_param_set_int(sess->params, "FirstBurstLength", + sess->FirstBurstLength); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + rc = iscsi_param_set_int(sess->params, "MaxBurstLength", + sess->MaxBurstLength); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + rc = iscsi_param_set(sess->params, "InitialR2T", + sess->InitialR2T ? "Yes" : "No"); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set() failed\n"); + goto error_return; + } + + rc = iscsi_param_set(sess->params, "ImmediateData", + sess->ImmediateData ? "Yes" : "No"); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set() failed\n"); + goto error_return; + } + + rc = iscsi_param_set(sess->params, "DataPDUInOrder", + sess->DataPDUInOrder ? "Yes" : "No"); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set() failed\n"); + goto error_return; + } + + rc = iscsi_param_set(sess->params, "DataSequenceInOrder", + sess->DataSequenceInOrder ? "Yes" : "No"); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set() failed\n"); + goto error_return; + } + + rc = iscsi_param_set_int(sess->params, "ErrorRecoveryLevel", + sess->ErrorRecoveryLevel); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + /* realloc buffer */ + rc = iscsi_param_set_int(conn->params, "MaxRecvDataSegmentLength", + conn->MaxRecvDataSegmentLength); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + /* sess for first connection of session */ + conn->sess = sess; + return 0; + +error_return: + iscsi_free_sess(sess); + conn->sess = NULL; + return -1; +} + +static struct spdk_iscsi_sess * +get_iscsi_sess_by_tsih(uint16_t tsih) +{ + struct spdk_iscsi_sess *session; + + if (tsih == 0 || tsih > g_iscsi.MaxSessions) { + return NULL; + } + + session = g_iscsi.session[tsih - 1]; + assert(tsih == session->tsih); + + return session; +} + +static uint8_t +append_iscsi_sess(struct spdk_iscsi_conn *conn, + const char *initiator_port_name, uint16_t tsih, uint16_t cid) +{ + struct spdk_iscsi_sess *sess; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "append session: init port name=%s, tsih=%u, cid=%u\n", + initiator_port_name, tsih, cid); + + sess = get_iscsi_sess_by_tsih(tsih); + if (sess == NULL) { + SPDK_ERRLOG("spdk_get_iscsi_sess_by_tsih failed\n"); + return ISCSI_LOGIN_CONN_ADD_FAIL; + } + if ((conn->pg_tag != sess->tag) || + (strcasecmp(initiator_port_name, spdk_scsi_port_get_name(sess->initiator_port)) != 0) || + (conn->target != sess->target)) { + /* no match */ + SPDK_ERRLOG("no MCS session for init port name=%s, tsih=%d, cid=%d\n", + initiator_port_name, tsih, cid); + return ISCSI_LOGIN_CONN_ADD_FAIL; + } + + if (sess->connections >= sess->MaxConnections) { + /* no slot for connection */ + SPDK_ERRLOG("too many connections for init port name=%s, tsih=%d, cid=%d\n", + initiator_port_name, tsih, cid); + return ISCSI_LOGIN_TOO_MANY_CONNECTIONS; + } + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Connections (tsih %d): %d\n", sess->tsih, sess->connections); + conn->sess = sess; + + /* + * TODO: need a mutex or other sync mechanism to protect the session's + * connection list. + */ + sess->conns[sess->connections] = conn; + sess->connections++; + + return 0; +} + +static int +iscsi_append_text(struct spdk_iscsi_conn *conn __attribute__((__unused__)), + const char *key, const char *val, uint8_t *data, + int alloc_len, int data_len) +{ + int total; + int len; + + total = data_len; + if (alloc_len < 1) { + return 0; + } + if (total > alloc_len) { + total = alloc_len; + data[total - 1] = '\0'; + return total; + } + + if (alloc_len - total < 1) { + SPDK_ERRLOG("data space small %d\n", alloc_len); + return total; + } + len = snprintf((char *) data + total, alloc_len - total, "%s=%s", key, val); + total += len + 1; + + return total; +} + +static int +iscsi_append_param(struct spdk_iscsi_conn *conn, const char *key, + uint8_t *data, int alloc_len, int data_len) +{ + struct iscsi_param *param; + int rc; + + param = iscsi_param_find(conn->params, key); + if (param == NULL) { + param = iscsi_param_find(conn->sess->params, key); + if (param == NULL) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "no key %.64s\n", key); + return data_len; + } + } + rc = iscsi_append_text(conn, param->key, param->val, data, + alloc_len, data_len); + return rc; +} + +static int +iscsi_auth_params(struct spdk_iscsi_conn *conn, + struct iscsi_param *params, const char *method, uint8_t *data, + int alloc_len, int data_len) +{ + char *in_val; + char *in_next; + char *new_val; + const char *algorithm; + const char *name; + const char *response; + const char *identifier; + const char *challenge; + int total; + int rc; + + if (conn == NULL || params == NULL || method == NULL) { + return -1; + } + if (strcasecmp(method, "CHAP") == 0) { + /* method OK */ + } else { + SPDK_ERRLOG("unsupported AuthMethod %.64s\n", method); + return -1; + } + + total = data_len; + if (alloc_len < 1) { + return 0; + } + if (total > alloc_len) { + total = alloc_len; + data[total - 1] = '\0'; + return total; + } + + /* for temporary store */ + in_val = malloc(ISCSI_TEXT_MAX_VAL_LEN + 1); + if (!in_val) { + SPDK_ERRLOG("malloc() failed for temporary store\n"); + return -ENOMEM; + } + + /* CHAP method (RFC1994) */ + if ((algorithm = iscsi_param_get_val(params, "CHAP_A")) != NULL) { + if (conn->auth.chap_phase != ISCSI_CHAP_PHASE_WAIT_A) { + SPDK_ERRLOG("CHAP sequence error\n"); + goto error_return; + } + + /* CHAP_A is LIST type */ + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN + 1, "%s", algorithm); + in_next = in_val; + while ((new_val = spdk_strsepq(&in_next, ",")) != NULL) { + if (strcasecmp(new_val, "5") == 0) { + /* CHAP with MD5 */ + break; + } + } + if (new_val == NULL) { + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN + 1, "%s", "Reject"); + new_val = in_val; + iscsi_append_text(conn, "CHAP_A", new_val, + data, alloc_len, total); + goto error_return; + } + /* selected algorithm is 5 (MD5) */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "got CHAP_A=%s\n", new_val); + total = iscsi_append_text(conn, "CHAP_A", new_val, + data, alloc_len, total); + + /* Identifier is one octet */ + gen_random(conn->auth.chap_id, 1); + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN, "%d", + (int) conn->auth.chap_id[0]); + total = iscsi_append_text(conn, "CHAP_I", in_val, + data, alloc_len, total); + + /* Challenge Value is a variable stream of octets */ + /* (binary length MUST not exceed 1024 bytes) */ + conn->auth.chap_challenge_len = ISCSI_CHAP_CHALLENGE_LEN; + gen_random(conn->auth.chap_challenge, conn->auth.chap_challenge_len); + bin2hex(in_val, ISCSI_TEXT_MAX_VAL_LEN, + conn->auth.chap_challenge, conn->auth.chap_challenge_len); + total = iscsi_append_text(conn, "CHAP_C", in_val, + data, alloc_len, total); + + conn->auth.chap_phase = ISCSI_CHAP_PHASE_WAIT_NR; + } else if ((name = iscsi_param_get_val(params, "CHAP_N")) != NULL) { + uint8_t resmd5[SPDK_MD5DIGEST_LEN]; + uint8_t tgtmd5[SPDK_MD5DIGEST_LEN]; + struct spdk_md5ctx md5ctx; + size_t decoded_len = 0; + + if (conn->auth.chap_phase != ISCSI_CHAP_PHASE_WAIT_NR) { + SPDK_ERRLOG("CHAP sequence error\n"); + goto error_return; + } + + response = iscsi_param_get_val(params, "CHAP_R"); + if (response == NULL) { + SPDK_ERRLOG("no response\n"); + goto error_return; + } + if (response[0] == '0' && + (response[1] == 'x' || response[1] == 'X')) { + rc = hex2bin(resmd5, SPDK_MD5DIGEST_LEN, response); + if (rc < 0 || rc != SPDK_MD5DIGEST_LEN) { + SPDK_ERRLOG("response format error\n"); + goto error_return; + } + } else if (response[0] == '0' && + (response[1] == 'b' || response[1] == 'B')) { + response += 2; + rc = spdk_base64_decode(resmd5, &decoded_len, response); + if (rc < 0 || decoded_len != SPDK_MD5DIGEST_LEN) { + SPDK_ERRLOG("response format error\n"); + goto error_return; + } + } else { + SPDK_ERRLOG("response format error\n"); + goto error_return; + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "got CHAP_N/CHAP_R\n"); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "ag_tag=%d\n", conn->chap_group); + + rc = iscsi_chap_get_authinfo(&conn->auth, name, conn->chap_group); + if (rc < 0) { + /* SPDK_ERRLOG("auth user or secret is missing\n"); */ + SPDK_ERRLOG("iscsi_chap_get_authinfo() failed\n"); + goto error_return; + } + if (conn->auth.user[0] == '\0' || conn->auth.secret[0] == '\0') { + /* SPDK_ERRLOG("auth user or secret is missing\n"); */ + SPDK_ERRLOG("auth failed (name %.64s)\n", name); + goto error_return; + } + + md5init(&md5ctx); + /* Identifier */ + md5update(&md5ctx, conn->auth.chap_id, 1); + /* followed by secret */ + md5update(&md5ctx, conn->auth.secret, + strlen(conn->auth.secret)); + /* followed by Challenge Value */ + md5update(&md5ctx, conn->auth.chap_challenge, + conn->auth.chap_challenge_len); + /* tgtmd5 is expecting Response Value */ + md5final(tgtmd5, &md5ctx); + + bin2hex(in_val, ISCSI_TEXT_MAX_VAL_LEN, tgtmd5, SPDK_MD5DIGEST_LEN); + +#if 0 + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "tgtmd5=%s, resmd5=%s\n", in_val, response); + spdk_dump("tgtmd5", tgtmd5, SPDK_MD5DIGEST_LEN); + spdk_dump("resmd5", resmd5, SPDK_MD5DIGEST_LEN); +#endif + + /* compare MD5 digest */ + if (memcmp(tgtmd5, resmd5, SPDK_MD5DIGEST_LEN) != 0) { + /* not match */ + /* SPDK_ERRLOG("auth user or secret is missing\n"); */ + SPDK_ERRLOG("auth failed (name %.64s)\n", name); + goto error_return; + } + /* OK initiator's secret */ + conn->authenticated = true; + + /* mutual CHAP? */ + identifier = iscsi_param_get_val(params, "CHAP_I"); + if (identifier != NULL) { + conn->auth.chap_mid[0] = (uint8_t) strtol(identifier, NULL, 10); + challenge = iscsi_param_get_val(params, "CHAP_C"); + if (challenge == NULL) { + SPDK_ERRLOG("CHAP sequence error\n"); + goto error_return; + } + if (challenge[0] == '0' && + (challenge[1] == 'x' || challenge[1] == 'X')) { + rc = hex2bin(conn->auth.chap_mchallenge, + ISCSI_CHAP_CHALLENGE_LEN, challenge); + if (rc < 0) { + SPDK_ERRLOG("challenge format error\n"); + goto error_return; + } + conn->auth.chap_mchallenge_len = rc; + } else if (challenge[0] == '0' && + (challenge[1] == 'b' || challenge[1] == 'B')) { + challenge += 2; + rc = spdk_base64_decode(conn->auth.chap_mchallenge, + &decoded_len, challenge); + if (rc < 0) { + SPDK_ERRLOG("challenge format error\n"); + goto error_return; + } + conn->auth.chap_mchallenge_len = decoded_len; + } else { + SPDK_ERRLOG("challenge format error\n"); + goto error_return; + } +#if 0 + spdk_dump("MChallenge", conn->auth.chap_mchallenge, + conn->auth.chap_mchallenge_len); +#endif + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "got CHAP_I/CHAP_C\n"); + + if (conn->auth.muser[0] == '\0' || conn->auth.msecret[0] == '\0') { + /* SPDK_ERRLOG("mutual auth user or secret is missing\n"); */ + SPDK_ERRLOG("auth failed (name %.64s)\n", name); + goto error_return; + } + + md5init(&md5ctx); + /* Identifier */ + md5update(&md5ctx, conn->auth.chap_mid, 1); + /* followed by secret */ + md5update(&md5ctx, conn->auth.msecret, + strlen(conn->auth.msecret)); + /* followed by Challenge Value */ + md5update(&md5ctx, conn->auth.chap_mchallenge, + conn->auth.chap_mchallenge_len); + /* tgtmd5 is Response Value */ + md5final(tgtmd5, &md5ctx); + + bin2hex(in_val, ISCSI_TEXT_MAX_VAL_LEN, tgtmd5, SPDK_MD5DIGEST_LEN); + + total = iscsi_append_text(conn, "CHAP_N", + conn->auth.muser, data, alloc_len, total); + total = iscsi_append_text(conn, "CHAP_R", + in_val, data, alloc_len, total); + } else { + /* not mutual */ + if (conn->mutual_chap) { + SPDK_ERRLOG("required mutual CHAP\n"); + goto error_return; + } + } + + conn->auth.chap_phase = ISCSI_CHAP_PHASE_END; + } else { + /* not found CHAP keys */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "start CHAP\n"); + conn->auth.chap_phase = ISCSI_CHAP_PHASE_WAIT_A; + } + + free(in_val); + return total; + +error_return: + conn->auth.chap_phase = ISCSI_CHAP_PHASE_WAIT_A; + free(in_val); + return -1; +} + +static int +iscsi_check_values(struct spdk_iscsi_conn *conn) +{ + if (conn->sess->FirstBurstLength > conn->sess->MaxBurstLength) { + SPDK_ERRLOG("FirstBurstLength(%d) > MaxBurstLength(%d)\n", + conn->sess->FirstBurstLength, + conn->sess->MaxBurstLength); + return -1; + } + if (conn->sess->FirstBurstLength > g_iscsi.FirstBurstLength) { + SPDK_ERRLOG("FirstBurstLength(%d) > iSCSI target restriction(%d)\n", + conn->sess->FirstBurstLength, g_iscsi.FirstBurstLength); + return -1; + } + if (conn->sess->MaxBurstLength > 0x00ffffff) { + SPDK_ERRLOG("MaxBurstLength(%d) > 0x00ffffff\n", + conn->sess->MaxBurstLength); + return -1; + } + + if (conn->MaxRecvDataSegmentLength < 512) { + SPDK_ERRLOG("MaxRecvDataSegmentLength(%d) < 512\n", + conn->MaxRecvDataSegmentLength); + return -1; + } + if (conn->MaxRecvDataSegmentLength > 0x00ffffff) { + SPDK_ERRLOG("MaxRecvDataSegmentLength(%d) > 0x00ffffff\n", + conn->MaxRecvDataSegmentLength); + return -1; + } + return 0; +} + +static int +iscsi_conn_params_update(struct spdk_iscsi_conn *conn) +{ + int rc; + uint32_t recv_buf_size; + + /* update internal variables */ + rc = iscsi_copy_param2var(conn); + if (rc < 0) { + SPDK_ERRLOG("iscsi_copy_param2var() failed\n"); + if (conn->state < ISCSI_CONN_STATE_EXITING) { + conn->state = ISCSI_CONN_STATE_EXITING; + } + return rc; + } + + /* check value */ + rc = iscsi_check_values(conn); + if (rc < 0) { + SPDK_ERRLOG("iscsi_check_values() failed\n"); + if (conn->state < ISCSI_CONN_STATE_EXITING) { + conn->state = ISCSI_CONN_STATE_EXITING; + } + } + + /* The socket receive buffer may need to be adjusted based on the new parameters */ + + /* Don't allow the recv buffer to be 0 or very large. */ + recv_buf_size = spdk_max(0x1000, spdk_min(0x2000, conn->sess->FirstBurstLength)); + + /* Add in extra space for the PDU */ + recv_buf_size += ISCSI_BHS_LEN + ISCSI_AHS_LEN; + + if (conn->header_digest) { + recv_buf_size += ISCSI_DIGEST_LEN; + } + + if (conn->data_digest) { + recv_buf_size += ISCSI_DIGEST_LEN; + } + + /* Set up to buffer up to 4 commands with immediate data at once */ + if (spdk_sock_set_recvbuf(conn->sock, recv_buf_size * 4) < 0) { + /* Not fatal. */ + } + + return rc; +} + +static void +iscsi_conn_login_pdu_err_complete(void *arg) +{ + struct spdk_iscsi_conn *conn = arg; + + if (conn->full_feature) { + iscsi_conn_params_update(conn); + } +} + +static void +iscsi_conn_login_pdu_success_complete(void *arg) +{ + struct spdk_iscsi_conn *conn = arg; + + if (conn->state >= ISCSI_CONN_STATE_EXITING) { + /* Connection is being exited before this callback is executed. */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Connection is already exited.\n"); + return; + } + if (conn->full_feature) { + if (iscsi_conn_params_update(conn) != 0) { + return; + } + } + conn->state = ISCSI_CONN_STATE_RUNNING; + if (conn->full_feature != 0) { + iscsi_conn_schedule(conn); + } +} + +/* + * The response function of spdk_iscsi_op_login + */ +static void +iscsi_op_login_response(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, struct iscsi_param *params, + iscsi_conn_xfer_complete_cb cb_fn) +{ + struct iscsi_bhs_login_rsp *rsph; + + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + rsph->version_max = ISCSI_VERSION; + rsph->version_act = ISCSI_VERSION; + DSET24(rsph->data_segment_len, rsp_pdu->data_segment_len); + + to_be32(&rsph->stat_sn, conn->StatSN); + conn->StatSN++; + + if (conn->sess != NULL) { + to_be32(&rsph->exp_cmd_sn, conn->sess->ExpCmdSN); + to_be32(&rsph->max_cmd_sn, conn->sess->MaxCmdSN); + } else { + to_be32(&rsph->exp_cmd_sn, rsp_pdu->cmd_sn); + to_be32(&rsph->max_cmd_sn, rsp_pdu->cmd_sn); + } + + SPDK_LOGDUMP(SPDK_LOG_ISCSI, "PDU", (uint8_t *)rsph, ISCSI_BHS_LEN); + SPDK_LOGDUMP(SPDK_LOG_ISCSI, "DATA", rsp_pdu->data, rsp_pdu->data_segment_len); + + /* Set T/CSG/NSG to reserved if login error. */ + if (rsph->status_class != 0) { + rsph->flags &= ~ISCSI_LOGIN_TRANSIT; + rsph->flags &= ~ISCSI_LOGIN_CURRENT_STAGE_MASK; + rsph->flags &= ~ISCSI_LOGIN_NEXT_STAGE_MASK; + } + iscsi_param_free(params); + iscsi_conn_write_pdu(conn, rsp_pdu, cb_fn, conn); +} + +/* + * The function which is used to initialize the internal response data + * structure of iscsi login function. + * return: + * 0, success; + * otherwise, error; + */ +static int +iscsi_op_login_rsp_init(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *pdu, struct spdk_iscsi_pdu *rsp_pdu) +{ + struct iscsi_bhs_login_req *reqh; + struct iscsi_bhs_login_rsp *rsph; + + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + rsph->opcode = ISCSI_OP_LOGIN_RSP; + rsph->status_class = ISCSI_CLASS_SUCCESS; + rsph->status_detail = ISCSI_LOGIN_ACCEPT; + rsp_pdu->data_segment_len = 0; + + /* The default MaxRecvDataSegmentLength 8192 is used during login. - RFC3720 */ + rsp_pdu->data = calloc(1, 8192); + if (!rsp_pdu->data) { + SPDK_ERRLOG("calloc() failed for data segment\n"); + rsph->status_class = ISCSI_CLASS_TARGET_ERROR; + rsph->status_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + rsp_pdu->data_buf_len = 8192; + + reqh = (struct iscsi_bhs_login_req *)&pdu->bhs; + rsph->flags |= (reqh->flags & ISCSI_LOGIN_TRANSIT); + rsph->flags |= (reqh->flags & ISCSI_LOGIN_CONTINUE); + rsph->flags |= (reqh->flags & ISCSI_LOGIN_CURRENT_STAGE_MASK); + if (ISCSI_BHS_LOGIN_GET_TBIT(rsph->flags)) { + rsph->flags |= (reqh->flags & ISCSI_LOGIN_NEXT_STAGE_MASK); + } + + /* We don't need to convert from network byte order. Just store it */ + memcpy(&rsph->isid, reqh->isid, 6); + rsph->tsih = reqh->tsih; + rsph->itt = reqh->itt; + rsp_pdu->cmd_sn = from_be32(&reqh->cmd_sn); + + if (rsph->tsih) { + rsph->stat_sn = reqh->exp_stat_sn; + } + + SPDK_LOGDUMP(SPDK_LOG_ISCSI, "PDU", (uint8_t *)&pdu->bhs, ISCSI_BHS_LEN); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "T=%d, C=%d, CSG=%d, NSG=%d, Min=%d, Max=%d, ITT=%x\n", + ISCSI_BHS_LOGIN_GET_TBIT(rsph->flags), + ISCSI_BHS_LOGIN_GET_CBIT(rsph->flags), + ISCSI_BHS_LOGIN_GET_CSG(rsph->flags), + ISCSI_BHS_LOGIN_GET_NSG(rsph->flags), + reqh->version_min, reqh->version_max, from_be32(&rsph->itt)); + + if (conn->sess != NULL) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "CmdSN=%u, ExpStatSN=%u, StatSN=%u, ExpCmdSN=%u," + "MaxCmdSN=%u\n", rsp_pdu->cmd_sn, + from_be32(&rsph->stat_sn), conn->StatSN, + conn->sess->ExpCmdSN, + conn->sess->MaxCmdSN); + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "CmdSN=%u, ExpStatSN=%u, StatSN=%u\n", + rsp_pdu->cmd_sn, from_be32(&rsph->stat_sn), + conn->StatSN); + } + + if (ISCSI_BHS_LOGIN_GET_TBIT(rsph->flags) && + ISCSI_BHS_LOGIN_GET_CBIT(rsph->flags)) { + SPDK_ERRLOG("transit error\n"); + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_INITIATOR_ERROR; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + /* make sure reqh->version_max < ISCSI_VERSION */ + if (reqh->version_min > ISCSI_VERSION) { + SPDK_ERRLOG("unsupported version min %d/max %d, expecting %d\n", reqh->version_min, + reqh->version_max, ISCSI_VERSION); + /* Unsupported version */ + /* set all reserved flag to zero */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_UNSUPPORTED_VERSION; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + + if ((ISCSI_BHS_LOGIN_GET_NSG(rsph->flags) == ISCSI_NSG_RESERVED_CODE) && + ISCSI_BHS_LOGIN_GET_TBIT(rsph->flags)) { + /* set NSG to zero */ + rsph->flags &= ~ISCSI_LOGIN_NEXT_STAGE_MASK; + /* also set other bits to zero */ + rsph->flags &= ~ISCSI_LOGIN_TRANSIT; + rsph->flags &= ~ISCSI_LOGIN_CURRENT_STAGE_MASK; + SPDK_ERRLOG("Received reserved NSG code: %d\n", ISCSI_NSG_RESERVED_CODE); + /* Initiator error */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_INITIATOR_ERROR; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + + return 0; +} + +static int +iscsi_op_login_store_incoming_params(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *pdu, struct spdk_iscsi_pdu *rsp_pdu, + struct iscsi_param **params) +{ + struct iscsi_bhs_login_req *reqh; + struct iscsi_bhs_login_rsp *rsph; + int rc; + + reqh = (struct iscsi_bhs_login_req *)&pdu->bhs; + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + + rc = iscsi_parse_params(params, pdu->data, + pdu->data_segment_len, ISCSI_BHS_LOGIN_GET_CBIT(reqh->flags), + &conn->partial_text_parameter); + if (rc < 0) { + SPDK_ERRLOG("iscsi_parse_params() failed\n"); + iscsi_param_free(*params); + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_INITIATOR_ERROR; + return SPDK_ISCSI_LOGIN_ERROR_PARAMETER; + } + + return 0; +} + +/* + * This function is used to initialize the port info + * return + * 0: success + * otherwise: error + */ +static int +iscsi_op_login_initialize_port(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + char *initiator_port_name, + uint32_t name_length, + struct iscsi_param *params) +{ + const char *val; + struct iscsi_bhs_login_rsp *rsph; + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + + /* Initiator Name and Port */ + val = iscsi_param_get_val(params, "InitiatorName"); + if (val == NULL) { + SPDK_ERRLOG("InitiatorName is empty\n"); + /* Missing parameter */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_MISSING_PARMS; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + snprintf(conn->initiator_name, sizeof(conn->initiator_name), "%s", val); + snprintf(initiator_port_name, name_length, + "%s,i,0x%12.12" PRIx64, val, iscsi_get_isid(rsph->isid)); + spdk_strlwr(conn->initiator_name); + spdk_strlwr(initiator_port_name); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Initiator name: %s\n", conn->initiator_name); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Initiator port: %s\n", initiator_port_name); + + return 0; +} + +/* + * This function is used to judge the session type + * return + * 0: success + * Other value: error + */ +static int +iscsi_op_login_session_type(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + enum session_type *session_type, + struct iscsi_param *params) +{ + const char *session_type_str; + struct iscsi_bhs_login_rsp *rsph; + + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + session_type_str = iscsi_param_get_val(params, "SessionType"); + if (session_type_str == NULL) { + if (rsph->tsih != 0) { + *session_type = SESSION_TYPE_NORMAL; + } else { + SPDK_ERRLOG("SessionType is empty\n"); + /* Missing parameter */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_MISSING_PARMS; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + } else { + if (strcasecmp(session_type_str, "Discovery") == 0) { + *session_type = SESSION_TYPE_DISCOVERY; + } else if (strcasecmp(session_type_str, "Normal") == 0) { + *session_type = SESSION_TYPE_NORMAL; + } else { + *session_type = SESSION_TYPE_INVALID; + SPDK_ERRLOG("SessionType is invalid\n"); + /* Missing parameter */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_MISSING_PARMS; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Session Type: %s\n", session_type_str); + + return 0; +} + +/* + * This function is used to check the target info + * return: + * 0: success + * otherwise: error + */ +static int +iscsi_op_login_check_target(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + const char *target_name, + struct spdk_iscsi_tgt_node **target) +{ + bool result; + struct iscsi_bhs_login_rsp *rsph; + + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + *target = iscsi_find_tgt_node(target_name); + if (*target == NULL) { + SPDK_WARNLOG("target %s not found\n", target_name); + /* Not found */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_TARGET_NOT_FOUND; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + if (iscsi_tgt_node_is_destructed(*target)) { + SPDK_ERRLOG("target %s is removed\n", target_name); + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_TARGET_REMOVED; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + result = iscsi_tgt_node_access(conn, *target, + conn->initiator_name, + conn->initiator_addr); + if (!result) { + SPDK_ERRLOG("access denied\n"); + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_AUTHORIZATION_FAIL; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + + return 0; +} + +/* + * This function use to check the session + * return: + * 0, success + * otherwise: error + */ +static int +iscsi_op_login_check_session(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + char *initiator_port_name, int cid) + +{ + int rc = 0; + struct iscsi_bhs_login_rsp *rsph; + + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + /* check existing session */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "isid=%"PRIx64", tsih=%u, cid=%u\n", + iscsi_get_isid(rsph->isid), from_be16(&rsph->tsih), cid); + if (rsph->tsih != 0) { + /* multiple connections */ + rc = append_iscsi_sess(conn, initiator_port_name, + from_be16(&rsph->tsih), cid); + if (rc != 0) { + SPDK_ERRLOG("isid=%"PRIx64", tsih=%u, cid=%u:" + "spdk_append_iscsi_sess() failed\n", + iscsi_get_isid(rsph->isid), from_be16(&rsph->tsih), + cid); + /* Can't include in session */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = rc; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + } else if (!g_iscsi.AllowDuplicateIsid) { + /* new session, drop old sess by the initiator */ + iscsi_drop_conns(conn, initiator_port_name, 0 /* drop old */); + } + + return rc; +} + +/* + * This function is used to del the original param and update it with new + * value + * return: + * 0: success + * otherwise: error + */ +static int +iscsi_op_login_update_param(struct spdk_iscsi_conn *conn, + const char *key, const char *value, + const char *list) +{ + int rc = 0; + struct iscsi_param *new_param, *orig_param; + int index; + + orig_param = iscsi_param_find(conn->params, key); + if (orig_param == NULL) { + SPDK_ERRLOG("orig_param %s not found\n", key); + return SPDK_ISCSI_LOGIN_ERROR_PARAMETER; + } + + index = orig_param->state_index; + rc = iscsi_param_del(&conn->params, key); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_del(%s) failed\n", key); + return SPDK_ISCSI_LOGIN_ERROR_PARAMETER; + } + rc = iscsi_param_add(&conn->params, key, value, list, ISPT_LIST); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_add() failed\n"); + return SPDK_ISCSI_LOGIN_ERROR_PARAMETER; + } + new_param = iscsi_param_find(conn->params, key); + if (new_param == NULL) { + SPDK_ERRLOG("iscsi_param_find() failed\n"); + return SPDK_ISCSI_LOGIN_ERROR_PARAMETER; + } + new_param->state_index = index; + return rc; +} + +static int +iscsi_negotiate_chap_param(struct spdk_iscsi_conn *conn) +{ + int rc = 0; + + if (conn->disable_chap) { + rc = iscsi_op_login_update_param(conn, "AuthMethod", "None", "None"); + } else if (conn->require_chap) { + rc = iscsi_op_login_update_param(conn, "AuthMethod", "CHAP", "CHAP"); + } + + return rc; +} + +/* + * The function which is used to handle the part of session discovery + * return: + * 0, success; + * otherwise: error; + */ +static int +iscsi_op_login_session_discovery_chap(struct spdk_iscsi_conn *conn) +{ + return iscsi_negotiate_chap_param(conn); +} + +/* + * This function is used to update the param related with chap + * return: + * 0: success + * otherwise: error + */ +static int +iscsi_op_login_negotiate_chap_param(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_tgt_node *target) +{ + conn->disable_chap = target->disable_chap; + conn->require_chap = target->require_chap; + conn->mutual_chap = target->mutual_chap; + conn->chap_group = target->chap_group; + + return iscsi_negotiate_chap_param(conn); +} + +static int +iscsi_op_login_negotiate_digest_param(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_tgt_node *target) +{ + int rc; + + if (target->header_digest) { + /* + * User specified header digests, so update the list of + * HeaderDigest values to remove "None" so that only + * initiators who support CRC32C can connect. + */ + rc = iscsi_op_login_update_param(conn, "HeaderDigest", "CRC32C", "CRC32C"); + if (rc < 0) { + return rc; + } + } + + if (target->data_digest) { + /* + * User specified data digests, so update the list of + * DataDigest values to remove "None" so that only + * initiators who support CRC32C can connect. + */ + rc = iscsi_op_login_update_param(conn, "DataDigest", "CRC32C", "CRC32C"); + if (rc < 0) { + return rc; + } + } + + return 0; +} + +/* + * The function which is used to handle the part of normal login session + * return: + * 0, success; + * SPDK_ISCSI_LOGIN_ERROR_PARAMETER, parameter error; + */ +static int +iscsi_op_login_session_normal(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + char *initiator_port_name, + struct iscsi_param *params, + int cid) +{ + struct spdk_iscsi_tgt_node *target = NULL; + const char *target_name; + const char *target_short_name; + struct iscsi_bhs_login_rsp *rsph; + int rc = 0; + + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + target_name = iscsi_param_get_val(params, "TargetName"); + + if (target_name == NULL) { + SPDK_ERRLOG("TargetName is empty\n"); + /* Missing parameter */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_MISSING_PARMS; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + + memset(conn->target_short_name, 0, MAX_TARGET_NAME); + target_short_name = strstr(target_name, ":"); + if (target_short_name != NULL) { + target_short_name++; /* Advance past the ':' */ + if (strlen(target_short_name) >= MAX_TARGET_NAME) { + SPDK_ERRLOG("Target Short Name (%s) is more than %u characters\n", + target_short_name, MAX_TARGET_NAME); + /* Invalid request */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_INVALID_LOGIN_REQUEST; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + snprintf(conn->target_short_name, MAX_TARGET_NAME, "%s", + target_short_name); + } + + pthread_mutex_lock(&g_iscsi.mutex); + rc = iscsi_op_login_check_target(conn, rsp_pdu, target_name, &target); + pthread_mutex_unlock(&g_iscsi.mutex); + + if (rc < 0) { + return rc; + } + + conn->target = target; + conn->dev = target->dev; + conn->target_port = spdk_scsi_dev_find_port_by_id(target->dev, + conn->pg_tag); + + rc = iscsi_op_login_check_session(conn, rsp_pdu, + initiator_port_name, cid); + if (rc < 0) { + return rc; + } + + /* force target flags */ + pthread_mutex_lock(&target->mutex); + rc = iscsi_op_login_negotiate_chap_param(conn, target); + pthread_mutex_unlock(&target->mutex); + + if (rc == 0) { + rc = iscsi_op_login_negotiate_digest_param(conn, target); + } + + if (rc != 0) { + /* Invalid request */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_INVALID_LOGIN_REQUEST; + } + + return rc; +} + +/* + * This function is used to set the info in the connection data structure + * return + * 0: success + * otherwise: error + */ +static int +iscsi_op_login_set_conn_info(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + char *initiator_port_name, + enum session_type session_type, int cid) +{ + int rc = 0; + struct spdk_iscsi_tgt_node *target; + struct iscsi_bhs_login_rsp *rsph; + struct spdk_scsi_port *initiator_port; + + target = conn->target; + + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + conn->authenticated = false; + conn->auth.chap_phase = ISCSI_CHAP_PHASE_WAIT_A; + conn->cid = cid; + + if (conn->sess == NULL) { + /* create initiator port */ + initiator_port = spdk_scsi_port_create(iscsi_get_isid(rsph->isid), 0, initiator_port_name); + if (initiator_port == NULL) { + SPDK_ERRLOG("create_port() failed\n"); + rsph->status_class = ISCSI_CLASS_TARGET_ERROR; + rsph->status_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + + /* new session */ + rc = create_iscsi_sess(conn, target, session_type); + if (rc < 0) { + spdk_scsi_port_free(&initiator_port); + SPDK_ERRLOG("create_sess() failed\n"); + rsph->status_class = ISCSI_CLASS_TARGET_ERROR; + rsph->status_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + /* initialize parameters */ + conn->sess->initiator_port = initiator_port; + conn->StatSN = from_be32(&rsph->stat_sn); + conn->sess->isid = iscsi_get_isid(rsph->isid); + + /* Initiator port TransportID */ + spdk_scsi_port_set_iscsi_transport_id(conn->sess->initiator_port, + conn->initiator_name, + conn->sess->isid); + + /* Discovery sessions will not have a target. */ + if (target != NULL) { + conn->sess->queue_depth = target->queue_depth; + } else { + /* + * Assume discovery sessions have an effective command + * windows size of 1. + */ + conn->sess->queue_depth = 1; + } + conn->sess->ExpCmdSN = rsp_pdu->cmd_sn; + conn->sess->MaxCmdSN = rsp_pdu->cmd_sn + conn->sess->queue_depth - 1; + } + + conn->initiator_port = conn->sess->initiator_port; + + return 0; +} + +/* + * This function is used to set the target info + * return + * 0: success + * otherwise: error + */ +static int +iscsi_op_login_set_target_info(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + enum session_type session_type) +{ + char buf[MAX_TMPBUF]; + const char *val; + int rc = 0; + struct spdk_iscsi_tgt_node *target = conn->target; + + /* declarative parameters */ + if (target != NULL) { + pthread_mutex_lock(&target->mutex); + if (target->alias[0] != '\0') { + snprintf(buf, sizeof buf, "%s", target->alias); + } else { + snprintf(buf, sizeof buf, "%s", ""); + } + pthread_mutex_unlock(&target->mutex); + rc = iscsi_param_set(conn->sess->params, "TargetAlias", buf); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set() failed\n"); + return SPDK_ISCSI_LOGIN_ERROR_PARAMETER; + } + } + snprintf(buf, sizeof buf, "%s:%s,%d", conn->portal_host, conn->portal_port, + conn->pg_tag); + rc = iscsi_param_set(conn->sess->params, "TargetAddress", buf); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set() failed\n"); + return SPDK_ISCSI_LOGIN_ERROR_PARAMETER; + } + snprintf(buf, sizeof buf, "%d", conn->pg_tag); + rc = iscsi_param_set(conn->sess->params, "TargetPortalGroupTag", buf); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set() failed\n"); + return SPDK_ISCSI_LOGIN_ERROR_PARAMETER; + } + + /* write in response */ + if (target != NULL) { + val = iscsi_param_get_val(conn->sess->params, "TargetAlias"); + if (val != NULL && strlen(val) != 0) { + rsp_pdu->data_segment_len = iscsi_append_param(conn, + "TargetAlias", + rsp_pdu->data, + rsp_pdu->data_buf_len, + rsp_pdu->data_segment_len); + } + if (session_type == SESSION_TYPE_DISCOVERY) { + rsp_pdu->data_segment_len = iscsi_append_param(conn, + "TargetAddress", + rsp_pdu->data, + rsp_pdu->data_buf_len, + rsp_pdu->data_segment_len); + } + rsp_pdu->data_segment_len = iscsi_append_param(conn, + "TargetPortalGroupTag", + rsp_pdu->data, + rsp_pdu->data_buf_len, + rsp_pdu->data_segment_len); + } + + return rc; +} + +/* + * This function is used to handle the login of iscsi initiator when there is + * no session + * return: + * 0, success; + * SPDK_ISCSI_LOGIN_ERROR_PARAMETER, parameter error; + * SPDK_ISCSI_LOGIN_ERROR_RESPONSE, used to notify the login fail. + */ +static int +iscsi_op_login_phase_none(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + struct iscsi_param *params, int cid) +{ + enum session_type session_type; + char initiator_port_name[MAX_INITIATOR_PORT_NAME]; + struct iscsi_bhs_login_rsp *rsph; + int rc = 0; + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + + conn->target = NULL; + conn->dev = NULL; + + rc = iscsi_op_login_initialize_port(conn, rsp_pdu, initiator_port_name, + MAX_INITIATOR_PORT_NAME, params); + if (rc < 0) { + return rc; + } + + rc = iscsi_op_login_session_type(conn, rsp_pdu, &session_type, params); + if (rc < 0) { + return rc; + } + + /* Target Name and Port */ + if (session_type == SESSION_TYPE_NORMAL) { + rc = iscsi_op_login_session_normal(conn, rsp_pdu, + initiator_port_name, + params, cid); + if (rc < 0) { + return rc; + } + + } else if (session_type == SESSION_TYPE_DISCOVERY) { + rsph->tsih = 0; + + /* force target flags */ + pthread_mutex_lock(&g_iscsi.mutex); + rc = iscsi_op_login_session_discovery_chap(conn); + pthread_mutex_unlock(&g_iscsi.mutex); + if (rc < 0) { + return rc; + } + } else { + SPDK_ERRLOG("unknown session type\n"); + /* Missing parameter */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_MISSING_PARMS; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + + rc = iscsi_op_login_set_conn_info(conn, rsp_pdu, initiator_port_name, + session_type, cid); + if (rc < 0) { + return rc; + } + + /* limit conns on discovery session */ + if (session_type == SESSION_TYPE_DISCOVERY) { + conn->sess->MaxConnections = 1; + rc = iscsi_param_set_int(conn->sess->params, + "MaxConnections", + conn->sess->MaxConnections); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + return SPDK_ISCSI_LOGIN_ERROR_PARAMETER; + } + } + + return iscsi_op_login_set_target_info(conn, rsp_pdu, session_type); +} + +/* + * This function is used to set the csg bit case in rsp + * return: + * 0, success + * otherwise: error + */ +static int +iscsi_op_login_rsp_handle_csg_bit(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + struct iscsi_param *params) +{ + const char *auth_method; + int rc; + struct iscsi_bhs_login_rsp *rsph; + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + + switch (ISCSI_BHS_LOGIN_GET_CSG(rsph->flags)) { + case ISCSI_SECURITY_NEGOTIATION_PHASE: + /* SecurityNegotiation */ + auth_method = iscsi_param_get_val(conn->params, "AuthMethod"); + if (auth_method == NULL) { + SPDK_ERRLOG("AuthMethod is empty\n"); + /* Missing parameter */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_MISSING_PARMS; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + if (strcasecmp(auth_method, "None") == 0) { + conn->authenticated = true; + } else { + rc = iscsi_auth_params(conn, params, auth_method, + rsp_pdu->data, rsp_pdu->data_buf_len, + rsp_pdu->data_segment_len); + if (rc < 0) { + SPDK_ERRLOG("iscsi_auth_params() failed\n"); + /* Authentication failure */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_AUTHENT_FAIL; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + rsp_pdu->data_segment_len = rc; + if (!conn->authenticated) { + /* not complete */ + rsph->flags &= ~ISCSI_LOGIN_TRANSIT; + } else { + if (conn->auth.chap_phase != ISCSI_CHAP_PHASE_END) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "CHAP phase not complete"); + } + } + + SPDK_LOGDUMP(SPDK_LOG_ISCSI, "Negotiated Auth Params", + rsp_pdu->data, rsp_pdu->data_segment_len); + } + break; + + case ISCSI_OPERATIONAL_NEGOTIATION_PHASE: + /* LoginOperationalNegotiation */ + if (conn->state == ISCSI_CONN_STATE_INVALID) { + if (conn->require_chap) { + /* Authentication failure */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_AUTHENT_FAIL; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } else { + /* AuthMethod=None */ + conn->authenticated = true; + } + } + if (!conn->authenticated) { + SPDK_ERRLOG("authentication error\n"); + /* Authentication failure */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_AUTHENT_FAIL; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + break; + + case ISCSI_FULL_FEATURE_PHASE: + /* FullFeaturePhase */ + SPDK_ERRLOG("XXX Login in FullFeaturePhase\n"); + /* Initiator error */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_INITIATOR_ERROR; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + + default: + SPDK_ERRLOG("unknown stage\n"); + /* Initiator error */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_INITIATOR_ERROR; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + + return 0; +} + +/* This function is used to notify the session info + * return + * 0: success + * otherwise: error + */ +static int +iscsi_op_login_notify_session_info(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu) +{ + struct iscsi_bhs_login_rsp *rsph; + + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + if (conn->sess->session_type == SESSION_TYPE_NORMAL) { + /* normal session */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Login from %s (%s) on %s tgt_node%d" + " (%s:%s,%d), ISID=%"PRIx64", TSIH=%u," + " CID=%u, HeaderDigest=%s, DataDigest=%s\n", + conn->initiator_name, conn->initiator_addr, + conn->target->name, conn->target->num, + conn->portal_host, conn->portal_port, conn->pg_tag, + conn->sess->isid, conn->sess->tsih, conn->cid, + (iscsi_param_eq_val(conn->params, "HeaderDigest", "CRC32C") + ? "on" : "off"), + (iscsi_param_eq_val(conn->params, "DataDigest", "CRC32C") + ? "on" : "off")); + } else if (conn->sess->session_type == SESSION_TYPE_DISCOVERY) { + /* discovery session */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Login(discovery) from %s (%s) on" + " (%s:%s,%d), ISID=%"PRIx64", TSIH=%u," + " CID=%u, HeaderDigest=%s, DataDigest=%s\n", + conn->initiator_name, conn->initiator_addr, + conn->portal_host, conn->portal_port, conn->pg_tag, + conn->sess->isid, conn->sess->tsih, conn->cid, + (iscsi_param_eq_val(conn->params, "HeaderDigest", "CRC32C") + ? "on" : "off"), + (iscsi_param_eq_val(conn->params, "DataDigest", "CRC32C") + ? "on" : "off")); + } else { + SPDK_ERRLOG("unknown session type\n"); + /* Initiator error */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_INITIATOR_ERROR; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + + return 0; +} + +/* + * This function is to handle the tbit cases + * return + * 0: success + * otherwise error + */ +static int +iscsi_op_login_rsp_handle_t_bit(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu) +{ + int rc; + struct iscsi_bhs_login_rsp *rsph; + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + + switch (ISCSI_BHS_LOGIN_GET_NSG(rsph->flags)) { + case ISCSI_SECURITY_NEGOTIATION_PHASE: + /* SecurityNegotiation */ + conn->login_phase = ISCSI_SECURITY_NEGOTIATION_PHASE; + break; + + case ISCSI_OPERATIONAL_NEGOTIATION_PHASE: + /* LoginOperationalNegotiation */ + conn->login_phase = ISCSI_OPERATIONAL_NEGOTIATION_PHASE; + break; + + case ISCSI_FULL_FEATURE_PHASE: + /* FullFeaturePhase */ + conn->login_phase = ISCSI_FULL_FEATURE_PHASE; + to_be16(&rsph->tsih, conn->sess->tsih); + + rc = iscsi_op_login_notify_session_info(conn, rsp_pdu); + if (rc < 0) { + return rc; + } + + conn->full_feature = 1; + break; + + default: + SPDK_ERRLOG("unknown stage\n"); + /* Initiator error */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_INITIATOR_ERROR; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + + return 0; +} + +/* + * This function is used to set the values of the internal data structure used + * by spdk_iscsi_op_login function + * return: + * 0, used to notify the a successful login + * SPDK_ISCSI_LOGIN_ERROR_RESPONSE, used to notify a failure login. + */ +static int +iscsi_op_login_rsp_handle(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, struct iscsi_param **params) +{ + int rc; + struct iscsi_bhs_login_rsp *rsph; + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + + /* negotiate parameters */ + rc = iscsi_negotiate_params(conn, params, rsp_pdu->data, + rsp_pdu->data_buf_len, + rsp_pdu->data_segment_len); + if (rc < 0) { + /* + * iscsi_negotiate_params just returns -1 on failure, + * so translate this into meaningful response codes and + * return values. + */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_INITIATOR_ERROR; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + + rsp_pdu->data_segment_len = rc; + SPDK_LOGDUMP(SPDK_LOG_ISCSI, "Negotiated Params", rsp_pdu->data, rc); + + /* handle the CSG bit case */ + rc = iscsi_op_login_rsp_handle_csg_bit(conn, rsp_pdu, *params); + if (rc < 0) { + return rc; + } + + /* handle the T bit case */ + if (ISCSI_BHS_LOGIN_GET_TBIT(rsph->flags)) { + rc = iscsi_op_login_rsp_handle_t_bit(conn, rsp_pdu); + } + + return rc; +} + +static int +iscsi_pdu_hdr_op_login(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + int rc; + struct iscsi_bhs_login_req *reqh; + struct spdk_iscsi_pdu *rsp_pdu; + + if (conn->full_feature && conn->sess != NULL && + conn->sess->session_type == SESSION_TYPE_DISCOVERY) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + + reqh = (struct iscsi_bhs_login_req *)&pdu->bhs; + pdu->cmd_sn = from_be32(&reqh->cmd_sn); + + /* During login processing, use the 8KB default FirstBurstLength as + * our maximum data segment length value. + */ + if (pdu->data_segment_len > SPDK_ISCSI_FIRST_BURST_LENGTH) { + return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + rsp_pdu = iscsi_get_pdu(conn); + if (rsp_pdu == NULL) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + rc = iscsi_op_login_rsp_init(conn, pdu, rsp_pdu); + if (rc < 0) { + iscsi_op_login_response(conn, rsp_pdu, NULL, iscsi_conn_login_pdu_err_complete); + return 0; + } + + conn->login_rsp_pdu = rsp_pdu; + return 0; +} + +static int +iscsi_pdu_payload_op_login(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + int rc; + struct iscsi_bhs_login_req *reqh; + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_param *params = NULL; + int cid; + + if (conn->login_rsp_pdu == NULL) { + return 0; + } + + rsp_pdu = conn->login_rsp_pdu; + + reqh = (struct iscsi_bhs_login_req *)&pdu->bhs; + cid = from_be16(&reqh->cid); + + rc = iscsi_op_login_store_incoming_params(conn, pdu, rsp_pdu, ¶ms); + if (rc < 0) { + iscsi_op_login_response(conn, rsp_pdu, NULL, iscsi_conn_login_pdu_err_complete); + return 0; + } + + if (conn->state == ISCSI_CONN_STATE_INVALID) { + rc = iscsi_op_login_phase_none(conn, rsp_pdu, params, cid); + if (rc == SPDK_ISCSI_LOGIN_ERROR_RESPONSE || rc == SPDK_ISCSI_LOGIN_ERROR_PARAMETER) { + iscsi_op_login_response(conn, rsp_pdu, params, iscsi_conn_login_pdu_err_complete); + return 0; + } + } + + rc = iscsi_op_login_rsp_handle(conn, rsp_pdu, ¶ms); + if (rc == SPDK_ISCSI_LOGIN_ERROR_RESPONSE) { + iscsi_op_login_response(conn, rsp_pdu, params, iscsi_conn_login_pdu_err_complete); + return 0; + } + + iscsi_op_login_response(conn, rsp_pdu, params, iscsi_conn_login_pdu_success_complete); + return 0; +} + +static int +iscsi_pdu_hdr_op_text(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + uint32_t task_tag; + uint32_t ExpStatSN; + int F_bit, C_bit; + struct iscsi_bhs_text_req *reqh; + + if (pdu->data_segment_len > iscsi_get_max_immediate_data_size()) { + SPDK_ERRLOG("data segment len(=%zu) > immediate data len(=%"PRIu32")\n", + pdu->data_segment_len, iscsi_get_max_immediate_data_size()); + return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + reqh = (struct iscsi_bhs_text_req *)&pdu->bhs; + + F_bit = !!(reqh->flags & ISCSI_FLAG_FINAL); + C_bit = !!(reqh->flags & ISCSI_TEXT_CONTINUE); + task_tag = from_be32(&reqh->itt); + ExpStatSN = from_be32(&reqh->exp_stat_sn); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "I=%d, F=%d, C=%d, ITT=%x, TTT=%x\n", + reqh->immediate, F_bit, C_bit, task_tag, from_be32(&reqh->ttt)); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "CmdSN=%u, ExpStatSN=%u, StatSN=%u, ExpCmdSN=%u, MaxCmdSN=%u\n", + pdu->cmd_sn, ExpStatSN, conn->StatSN, conn->sess->ExpCmdSN, + conn->sess->MaxCmdSN); + + if (ExpStatSN != conn->StatSN) { +#if 0 + SPDK_ERRLOG("StatSN(%u) error\n", ExpStatSN); + return -1; +#else + /* StarPort have a bug */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "StatSN(%u) rewound\n", ExpStatSN); + conn->StatSN = ExpStatSN; +#endif + } + + if (F_bit && C_bit) { + SPDK_ERRLOG("final and continue\n"); + return -1; + } + + /* + * If this is the first text op in a sequence, save the ITT so we can + * compare it against the ITT for subsequent ops in the same sequence. + * If a subsequent text op in same sequence has a different ITT, reject + * that PDU. + */ + if (conn->sess->current_text_itt == 0xffffffffU) { + conn->sess->current_text_itt = task_tag; + } else if (conn->sess->current_text_itt != task_tag) { + SPDK_ERRLOG("The correct itt is %u, and the current itt is %u...\n", + conn->sess->current_text_itt, task_tag); + return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + return 0; +} + +static void +iscsi_conn_text_pdu_complete(void *arg) +{ + struct spdk_iscsi_conn *conn = arg; + + iscsi_conn_params_update(conn); +} + +static int +iscsi_pdu_payload_op_text(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct iscsi_param *params = NULL; + struct spdk_iscsi_pdu *rsp_pdu; + uint8_t *data; + uint64_t lun; + uint32_t task_tag; + const char *val; + int F_bit, C_bit; + int data_len; + int alloc_len; + int rc; + struct iscsi_bhs_text_req *reqh; + struct iscsi_bhs_text_resp *rsph; + + data_len = 0; + alloc_len = conn->MaxRecvDataSegmentLength; + + reqh = (struct iscsi_bhs_text_req *)&pdu->bhs; + + F_bit = !!(reqh->flags & ISCSI_FLAG_FINAL); + C_bit = !!(reqh->flags & ISCSI_TEXT_CONTINUE); + lun = from_be64(&reqh->lun); + task_tag = from_be32(&reqh->itt); + + /* store incoming parameters */ + rc = iscsi_parse_params(¶ms, pdu->data, pdu->data_segment_len, + C_bit, &conn->partial_text_parameter); + if (rc < 0) { + SPDK_ERRLOG("iscsi_parse_params() failed\n"); + iscsi_param_free(params); + return -1; + } + + data = calloc(1, alloc_len); + if (!data) { + SPDK_ERRLOG("calloc() failed for data segment\n"); + iscsi_param_free(params); + return -ENOMEM; + } + + /* negotiate parameters */ + data_len = iscsi_negotiate_params(conn, ¶ms, + data, alloc_len, data_len); + if (data_len < 0) { + SPDK_ERRLOG("iscsi_negotiate_params() failed\n"); + iscsi_param_free(params); + free(data); + return -1; + } + + /* sendtargets is special case */ + val = iscsi_param_get_val(params, "SendTargets"); + if (val != NULL) { + if (iscsi_param_eq_val(conn->sess->params, + "SessionType", "Discovery")) { + if (strcasecmp(val, "") == 0) { + val = "ALL"; + } + + data_len = iscsi_send_tgts(conn, + conn->initiator_name, + conn->initiator_addr, + val, data, alloc_len, + data_len); + } else { + if (strcasecmp(val, "") == 0) { + val = conn->target->name; + } + + if (strcasecmp(val, "ALL") == 0) { + /* not in discovery session */ + data_len = iscsi_append_text(conn, + "SendTargets", + "Reject", data, + alloc_len, data_len); + } else { + data_len = iscsi_send_tgts(conn, + conn->initiator_name, + conn->initiator_addr, + val, data, alloc_len, + data_len); + } + } + } else { + if (iscsi_param_eq_val(conn->sess->params, "SessionType", "Discovery")) { + iscsi_param_free(params); + free(data); + return SPDK_ISCSI_CONNECTION_FATAL; + } + } + + iscsi_param_free(params); + SPDK_LOGDUMP(SPDK_LOG_ISCSI, "Negotiated Params", data, data_len); + + /* response PDU */ + rsp_pdu = iscsi_get_pdu(conn); + if (rsp_pdu == NULL) { + free(data); + return SPDK_ISCSI_CONNECTION_FATAL; + } + rsph = (struct iscsi_bhs_text_resp *)&rsp_pdu->bhs; + + rsp_pdu->data = data; + rsph->opcode = ISCSI_OP_TEXT_RSP; + + if (F_bit) { + rsph->flags |= ISCSI_FLAG_FINAL; + } + + if (C_bit) { + rsph->flags |= ISCSI_TEXT_CONTINUE; + } + + DSET24(rsph->data_segment_len, data_len); + to_be64(&rsph->lun, lun); + to_be32(&rsph->itt, task_tag); + + if (F_bit) { + rsph->ttt = 0xffffffffU; + conn->sess->current_text_itt = 0xffffffffU; + } else { + to_be32(&rsph->ttt, 1 + conn->id); + } + + to_be32(&rsph->stat_sn, conn->StatSN); + conn->StatSN++; + + if (reqh->immediate == 0) { + conn->sess->MaxCmdSN++; + } + + to_be32(&rsph->exp_cmd_sn, conn->sess->ExpCmdSN); + to_be32(&rsph->max_cmd_sn, conn->sess->MaxCmdSN); + + iscsi_conn_write_pdu(conn, rsp_pdu, iscsi_conn_text_pdu_complete, conn); + return 0; +} + +static void iscsi_conn_logout_pdu_complete(void *arg) +{ + struct spdk_iscsi_conn *conn = arg; + + if (conn->sess == NULL) { + /* + * login failed but initiator still sent a logout rather than + * just closing the TCP connection. + */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Logout(login failed) from %s (%s) on" + " (%s:%s,%d)\n", + conn->initiator_name, conn->initiator_addr, + conn->portal_host, conn->portal_port, conn->pg_tag); + } else if (iscsi_param_eq_val(conn->sess->params, "SessionType", "Normal")) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Logout from %s (%s) on %s tgt_node%d" + " (%s:%s,%d), ISID=%"PRIx64", TSIH=%u," + " CID=%u, HeaderDigest=%s, DataDigest=%s\n", + conn->initiator_name, conn->initiator_addr, + conn->target->name, conn->target->num, + conn->portal_host, conn->portal_port, conn->pg_tag, + conn->sess->isid, conn->sess->tsih, conn->cid, + (iscsi_param_eq_val(conn->params, "HeaderDigest", "CRC32C") + ? "on" : "off"), + (iscsi_param_eq_val(conn->params, "DataDigest", "CRC32C") + ? "on" : "off")); + } else { + /* discovery session */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Logout(discovery) from %s (%s) on" + " (%s:%s,%d), ISID=%"PRIx64", TSIH=%u," + " CID=%u, HeaderDigest=%s, DataDigest=%s\n", + conn->initiator_name, conn->initiator_addr, + conn->portal_host, conn->portal_port, conn->pg_tag, + conn->sess->isid, conn->sess->tsih, conn->cid, + (iscsi_param_eq_val(conn->params, "HeaderDigest", "CRC32C") + ? "on" : "off"), + (iscsi_param_eq_val(conn->params, "DataDigest", "CRC32C") + ? "on" : "off")); + } +} + +static int +iscsi_pdu_hdr_op_logout(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct spdk_iscsi_pdu *rsp_pdu; + uint32_t task_tag; + uint32_t ExpStatSN; + int response; + struct iscsi_bhs_logout_req *reqh; + struct iscsi_bhs_logout_resp *rsph; + uint16_t cid; + + reqh = (struct iscsi_bhs_logout_req *)&pdu->bhs; + + cid = from_be16(&reqh->cid); + task_tag = from_be32(&reqh->itt); + ExpStatSN = from_be32(&reqh->exp_stat_sn); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "reason=%d, ITT=%x, cid=%d\n", + reqh->reason, task_tag, cid); + + if (conn->sess != NULL) { + if (conn->sess->session_type == SESSION_TYPE_DISCOVERY && + reqh->reason != ISCSI_LOGOUT_REASON_CLOSE_SESSION) { + SPDK_ERRLOG("Target can accept logout only with reason \"close the session\" " + "on discovery session. %d is not acceptable reason.\n", + reqh->reason); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "CmdSN=%u, ExpStatSN=%u, StatSN=%u, ExpCmdSN=%u, MaxCmdSN=%u\n", + pdu->cmd_sn, ExpStatSN, conn->StatSN, + conn->sess->ExpCmdSN, conn->sess->MaxCmdSN); + + if (pdu->cmd_sn != conn->sess->ExpCmdSN) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "CmdSN(%u) might have dropped\n", pdu->cmd_sn); + /* ignore error */ + } + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "CmdSN=%u, ExpStatSN=%u, StatSN=%u\n", + pdu->cmd_sn, ExpStatSN, conn->StatSN); + } + + if (ExpStatSN != conn->StatSN) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "StatSN(%u/%u) might have dropped\n", + ExpStatSN, conn->StatSN); + /* ignore error */ + } + + if (conn->id == cid) { + /* connection or session closed successfully */ + response = 0; + iscsi_conn_logout(conn); + } else { + response = 1; + } + + /* response PDU */ + rsp_pdu = iscsi_get_pdu(conn); + if (rsp_pdu == NULL) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + rsph = (struct iscsi_bhs_logout_resp *)&rsp_pdu->bhs; + rsp_pdu->data = NULL; + rsph->opcode = ISCSI_OP_LOGOUT_RSP; + rsph->flags |= 0x80; /* bit 0 must be 1 */ + rsph->response = response; + DSET24(rsph->data_segment_len, 0); + to_be32(&rsph->itt, task_tag); + + if (conn->sess != NULL) { + to_be32(&rsph->stat_sn, conn->StatSN); + conn->StatSN++; + + if (conn->sess->connections == 1) { + conn->sess->MaxCmdSN++; + } + + to_be32(&rsph->exp_cmd_sn, conn->sess->ExpCmdSN); + to_be32(&rsph->max_cmd_sn, conn->sess->MaxCmdSN); + } else { + to_be32(&rsph->stat_sn, conn->StatSN); + conn->StatSN++; + to_be32(&rsph->exp_cmd_sn, pdu->cmd_sn); + to_be32(&rsph->max_cmd_sn, pdu->cmd_sn); + } + + rsph->time_2_wait = 0; + rsph->time_2_retain = 0; + + iscsi_conn_write_pdu(conn, rsp_pdu, iscsi_conn_logout_pdu_complete, conn); + + return 0; +} + +static int +iscsi_send_r2t(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task, int offset, + int len, uint32_t transfer_tag, uint32_t *R2TSN) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_r2t *rsph; + uint64_t fmt_lun; + + /* R2T PDU */ + rsp_pdu = iscsi_get_pdu(conn); + if (rsp_pdu == NULL) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + rsph = (struct iscsi_bhs_r2t *)&rsp_pdu->bhs; + rsp_pdu->data = NULL; + rsph->opcode = ISCSI_OP_R2T; + rsph->flags |= 0x80; /* bit 0 is default to 1 */ + fmt_lun = spdk_scsi_lun_id_int_to_fmt(task->lun_id); + to_be64(&rsph->lun, fmt_lun); + to_be32(&rsph->itt, task->tag); + to_be32(&rsph->ttt, transfer_tag); + + to_be32(&rsph->stat_sn, conn->StatSN); + to_be32(&rsph->exp_cmd_sn, conn->sess->ExpCmdSN); + to_be32(&rsph->max_cmd_sn, conn->sess->MaxCmdSN); + + to_be32(&rsph->r2t_sn, *R2TSN); + *R2TSN += 1; + + task->r2t_datasn = 0; /* next expected datasn to ack */ + + to_be32(&rsph->buffer_offset, (uint32_t)offset); + to_be32(&rsph->desired_xfer_len, (uint32_t)len); + task->desired_data_transfer_length = (size_t)len; + + /* we need to hold onto this task/cmd because until the PDU has been + * written out */ + rsp_pdu->task = task; + task->scsi.ref++; + + iscsi_conn_write_pdu(conn, rsp_pdu, iscsi_conn_pdu_generic_complete, NULL); + + return 0; +} + +/* This function is used to remove the r2t pdu from snack_pdu_list by < task, r2t_sn> info */ +static struct spdk_iscsi_pdu * +iscsi_remove_r2t_pdu_from_snack_list(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task, + uint32_t r2t_sn) +{ + struct spdk_iscsi_pdu *pdu; + struct iscsi_bhs_r2t *r2t_header; + + TAILQ_FOREACH(pdu, &conn->snack_pdu_list, tailq) { + if (pdu->bhs.opcode == ISCSI_OP_R2T) { + r2t_header = (struct iscsi_bhs_r2t *)&pdu->bhs; + if (pdu->task == task && + from_be32(&r2t_header->r2t_sn) == r2t_sn) { + TAILQ_REMOVE(&conn->snack_pdu_list, pdu, tailq); + return pdu; + } + } + } + + return NULL; +} + +/* This function is used re-send the r2t packet */ +static int +iscsi_send_r2t_recovery(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task, uint32_t r2t_sn, + bool send_new_r2tsn) +{ + struct spdk_iscsi_pdu *pdu; + struct iscsi_bhs_r2t *rsph; + uint32_t transfer_len; + uint32_t len; + int rc; + + /* remove the r2t pdu from the snack_list */ + pdu = iscsi_remove_r2t_pdu_from_snack_list(conn, task, r2t_sn); + if (!pdu) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "No pdu is found\n"); + return -1; + } + + /* flag + * false: only need to re-send the old r2t with changing statsn + * true: we send a r2t with new r2tsn + */ + if (!send_new_r2tsn) { + to_be32(&pdu->bhs.stat_sn, conn->StatSN); + iscsi_conn_write_pdu(conn, pdu, iscsi_conn_pdu_generic_complete, NULL); + } else { + rsph = (struct iscsi_bhs_r2t *)&pdu->bhs; + transfer_len = from_be32(&rsph->desired_xfer_len); + + /* still need to increase the acked r2tsn */ + task->acked_r2tsn++; + len = spdk_min(conn->sess->MaxBurstLength, + (transfer_len - task->next_expected_r2t_offset)); + + /* remove the old_r2t_pdu */ + iscsi_conn_free_pdu(conn, pdu); + + /* re-send a new r2t pdu */ + rc = iscsi_send_r2t(conn, task, task->next_expected_r2t_offset, + len, task->ttt, &task->R2TSN); + if (rc < 0) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + } + + return 0; +} + +static int +add_transfer_task(struct spdk_iscsi_conn *conn, struct spdk_iscsi_task *task) +{ + uint32_t transfer_len; + size_t max_burst_len; + size_t segment_len; + size_t data_len; + int len; + int rc; + int data_out_req; + + transfer_len = task->scsi.transfer_len; + data_len = iscsi_task_get_pdu(task)->data_segment_len; + max_burst_len = conn->sess->MaxBurstLength; + segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + data_out_req = 1 + (transfer_len - data_len - 1) / segment_len; + task->data_out_cnt = data_out_req; + + /* + * If we already have too many tasks using R2T, then queue this task + * and start sending R2T for it after some of the tasks using R2T/data + * out buffers complete. + */ + if (conn->pending_r2t >= DEFAULT_MAXR2T) { + TAILQ_INSERT_TAIL(&conn->queued_r2t_tasks, task, link); + return 0; + } + + conn->data_out_cnt += data_out_req; + conn->pending_r2t++; + + task->next_expected_r2t_offset = data_len; + task->current_r2t_length = 0; + task->R2TSN = 0; + /* According to RFC3720 10.8.5, 0xffffffff is + * reserved for TTT in R2T. + */ + if (++conn->ttt == 0xffffffffu) { + conn->ttt = 0; + } + task->ttt = conn->ttt; + + while (data_len != transfer_len) { + len = spdk_min(max_burst_len, (transfer_len - data_len)); + rc = iscsi_send_r2t(conn, task, data_len, len, + task->ttt, &task->R2TSN); + if (rc < 0) { + SPDK_ERRLOG("iscsi_send_r2t() failed\n"); + return rc; + } + data_len += len; + task->next_r2t_offset = data_len; + task->outstanding_r2t++; + if (conn->sess->MaxOutstandingR2T == task->outstanding_r2t) { + break; + } + } + + TAILQ_INSERT_TAIL(&conn->active_r2t_tasks, task, link); + task->is_r2t_active = true; + return 0; +} + +/* If there are additional large writes queued for R2Ts, start them now. + * This is called when a large write is just completed or when multiple LUNs + * are attached and large write tasks for the specific LUN are cleared. + */ +static void +start_queued_transfer_tasks(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_task *task, *tmp; + + TAILQ_FOREACH_SAFE(task, &conn->queued_r2t_tasks, link, tmp) { + if (conn->pending_r2t < DEFAULT_MAXR2T) { + TAILQ_REMOVE(&conn->queued_r2t_tasks, task, link); + add_transfer_task(conn, task); + } else { + break; + } + } +} + +bool +iscsi_del_transfer_task(struct spdk_iscsi_conn *conn, uint32_t task_tag) +{ + struct spdk_iscsi_task *task, *tmp; + + TAILQ_FOREACH_SAFE(task, &conn->active_r2t_tasks, link, tmp) { + if (task->tag == task_tag) { + assert(conn->data_out_cnt >= task->data_out_cnt); + conn->data_out_cnt -= task->data_out_cnt; + + conn->pending_r2t--; + + assert(task->is_r2t_active == true); + TAILQ_REMOVE(&conn->active_r2t_tasks, task, link); + task->is_r2t_active = false; + iscsi_task_put(task); + + start_queued_transfer_tasks(conn); + return true; + } + } + return false; +} + +void iscsi_clear_all_transfer_task(struct spdk_iscsi_conn *conn, + struct spdk_scsi_lun *lun, + struct spdk_iscsi_pdu *pdu) +{ + struct spdk_iscsi_task *task, *task_tmp; + struct spdk_iscsi_pdu *pdu_tmp; + + TAILQ_FOREACH_SAFE(task, &conn->active_r2t_tasks, link, task_tmp) { + pdu_tmp = iscsi_task_get_pdu(task); + if ((lun == NULL || lun == task->scsi.lun) && + (pdu == NULL || spdk_sn32_lt(pdu_tmp->cmd_sn, pdu->cmd_sn))) { + task->outstanding_r2t = 0; + task->next_r2t_offset = 0; + task->next_expected_r2t_offset = 0; + assert(conn->data_out_cnt >= task->data_out_cnt); + conn->data_out_cnt -= task->data_out_cnt; + conn->pending_r2t--; + + TAILQ_REMOVE(&conn->active_r2t_tasks, task, link); + task->is_r2t_active = false; + if (lun != NULL && spdk_scsi_lun_is_removing(lun)) { + spdk_scsi_task_process_null_lun(&task->scsi); + iscsi_task_response(conn, task); + } + iscsi_task_put(task); + } + } + + TAILQ_FOREACH_SAFE(task, &conn->queued_r2t_tasks, link, task_tmp) { + pdu_tmp = iscsi_task_get_pdu(task); + if ((lun == NULL || lun == task->scsi.lun) && + (pdu == NULL || spdk_sn32_lt(pdu_tmp->cmd_sn, pdu->cmd_sn))) { + TAILQ_REMOVE(&conn->queued_r2t_tasks, task, link); + task->is_r2t_active = false; + if (lun != NULL && spdk_scsi_lun_is_removing(lun)) { + spdk_scsi_task_process_null_lun(&task->scsi); + iscsi_task_response(conn, task); + } + iscsi_task_put(task); + } + } + + start_queued_transfer_tasks(conn); +} + +static struct spdk_iscsi_task * +get_transfer_task(struct spdk_iscsi_conn *conn, uint32_t transfer_tag) +{ + struct spdk_iscsi_task *task; + + TAILQ_FOREACH(task, &conn->active_r2t_tasks, link) { + if (task->ttt == transfer_tag) { + return task; + } + } + + return NULL; +} + +static void +iscsi_conn_datain_pdu_complete(void *arg) +{ + struct spdk_iscsi_conn *conn = arg; + + iscsi_conn_handle_queued_datain_tasks(conn); +} + +static int +iscsi_send_datain(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task, int datain_flag, + int residual_len, int offset, int DataSN, int len) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_data_in *rsph; + uint32_t task_tag; + uint32_t transfer_tag; + int F_bit, U_bit, O_bit, S_bit; + struct spdk_iscsi_task *primary; + struct spdk_scsi_lun *lun_dev; + + primary = iscsi_task_get_primary(task); + + /* DATA PDU */ + rsp_pdu = iscsi_get_pdu(conn); + rsph = (struct iscsi_bhs_data_in *)&rsp_pdu->bhs; + rsp_pdu->data = task->scsi.iovs[0].iov_base + offset; + rsp_pdu->data_buf_len = task->scsi.iovs[0].iov_len - offset; + rsp_pdu->data_from_mempool = true; + + task_tag = task->tag; + transfer_tag = 0xffffffffU; + + F_bit = datain_flag & ISCSI_FLAG_FINAL; + O_bit = datain_flag & ISCSI_DATAIN_OVERFLOW; + U_bit = datain_flag & ISCSI_DATAIN_UNDERFLOW; + S_bit = datain_flag & ISCSI_DATAIN_STATUS; + + /* + * we need to hold onto this task/cmd because until the + * PDU has been written out + */ + rsp_pdu->task = task; + task->scsi.ref++; + + rsph->opcode = ISCSI_OP_SCSI_DATAIN; + + if (F_bit) { + rsph->flags |= ISCSI_FLAG_FINAL; + } + + /* we leave the A_bit clear */ + + if (F_bit && S_bit) { + if (O_bit) { + rsph->flags |= ISCSI_DATAIN_OVERFLOW; + } + + if (U_bit) { + rsph->flags |= ISCSI_DATAIN_UNDERFLOW; + } + } + + if (S_bit) { + rsph->flags |= ISCSI_DATAIN_STATUS; + rsph->status = task->scsi.status; + } + + DSET24(rsph->data_segment_len, len); + + to_be32(&rsph->itt, task_tag); + to_be32(&rsph->ttt, transfer_tag); + + if (S_bit) { + to_be32(&rsph->stat_sn, conn->StatSN); + conn->StatSN++; + } + + if (F_bit && S_bit && !iscsi_task_is_immediate(primary)) { + conn->sess->MaxCmdSN++; + } + + to_be32(&rsph->exp_cmd_sn, conn->sess->ExpCmdSN); + to_be32(&rsph->max_cmd_sn, conn->sess->MaxCmdSN); + + to_be32(&rsph->data_sn, DataSN); + + if (conn->sess->ErrorRecoveryLevel >= 1) { + primary->datain_datasn = DataSN; + } + DataSN++; + + if (task->parent) { + offset += primary->scsi.data_transferred; + } + to_be32(&rsph->buffer_offset, (uint32_t)offset); + task->scsi.offset = offset; + + if (F_bit && S_bit) { + to_be32(&rsph->res_cnt, residual_len); + } + + lun_dev = spdk_scsi_dev_get_lun(conn->dev, task->lun_id); + if (spdk_likely(lun_dev != NULL)) { + if (spdk_unlikely(spdk_scsi_lun_get_dif_ctx(lun_dev, &task->scsi, + &rsp_pdu->dif_ctx))) { + rsp_pdu->dif_insert_or_strip = true; + } + } + + iscsi_conn_write_pdu(conn, rsp_pdu, iscsi_conn_datain_pdu_complete, conn); + + return DataSN; +} + +static int +iscsi_transfer_in(struct spdk_iscsi_conn *conn, struct spdk_iscsi_task *task) +{ + uint32_t DataSN; + uint32_t transfer_len; + uint32_t data_len; + uint32_t segment_len; + uint32_t offset; + uint32_t residual_len = 0; + int sent_status; + uint32_t len; + int datain_flag = 0; + int datain_seq_cnt; + int i; + uint32_t sequence_end; + struct spdk_iscsi_task *primary; + + primary = iscsi_task_get_primary(task); + segment_len = conn->MaxRecvDataSegmentLength; + data_len = task->scsi.data_transferred; + transfer_len = task->scsi.length; + + if (task->scsi.status != SPDK_SCSI_STATUS_GOOD) { + return 0; + } + + if (data_len < transfer_len) { + /* underflow */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Underflow %u/%u\n", data_len, transfer_len); + residual_len = transfer_len - data_len; + transfer_len = data_len; + datain_flag |= ISCSI_DATAIN_UNDERFLOW; + } else if (data_len > transfer_len) { + /* overflow */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Overflow %u/%u\n", data_len, transfer_len); + residual_len = data_len - transfer_len; + datain_flag |= ISCSI_DATAIN_OVERFLOW; + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Transfer %u\n", transfer_len); + residual_len = 0; + } + + DataSN = primary->datain_datasn; + sent_status = 0; + + /* calculate the number of sequences for all data-in pdus */ + datain_seq_cnt = 1 + ((transfer_len - 1) / (int)conn->sess->MaxBurstLength); + for (i = 0; i < datain_seq_cnt; i++) { + offset = i * conn->sess->MaxBurstLength; + sequence_end = spdk_min(((i + 1) * conn->sess->MaxBurstLength), + transfer_len); + + /* send data splitted by segment_len */ + for (; offset < sequence_end; offset += segment_len) { + len = spdk_min(segment_len, (sequence_end - offset)); + + datain_flag &= ~ISCSI_FLAG_FINAL; + datain_flag &= ~ISCSI_DATAIN_STATUS; + + if (offset + len == sequence_end) { + /* last PDU in a sequence */ + datain_flag |= ISCSI_FLAG_FINAL; + if (task->scsi.sense_data_len == 0) { + /* The last pdu in all data-in pdus */ + if ((offset + len) == transfer_len && + (primary->bytes_completed == primary->scsi.transfer_len)) { + datain_flag |= ISCSI_DATAIN_STATUS; + sent_status = 1; + } + } + } + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Transfer=%d, Offset=%d, Len=%d\n", + sequence_end, offset, len); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "StatSN=%u, DataSN=%u, Offset=%u, Len=%d\n", + conn->StatSN, DataSN, offset, len); + + DataSN = iscsi_send_datain(conn, task, datain_flag, residual_len, + offset, DataSN, len); + } + } + + if (task != primary) { + primary->scsi.data_transferred += task->scsi.data_transferred; + } + primary->datain_datasn = DataSN; + + return sent_status; +} + +void iscsi_task_response(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_scsi_resp *rsph; + uint32_t task_tag; + uint32_t transfer_len; + size_t residual_len; + size_t data_len; + int O_bit, U_bit; + int rc; + struct spdk_iscsi_task *primary; + + primary = iscsi_task_get_primary(task); + + transfer_len = primary->scsi.transfer_len; + task_tag = task->tag; + + /* transfer data from logical unit */ + /* (direction is view of initiator side) */ + if (iscsi_task_is_read(primary)) { + rc = iscsi_transfer_in(conn, task); + if (rc > 0) { + /* sent status by last DATAIN PDU */ + return; + } + + if (primary->bytes_completed != primary->scsi.transfer_len) { + return; + } + } + + O_bit = U_bit = 0; + residual_len = 0; + data_len = primary->scsi.data_transferred; + + if ((transfer_len != 0) && + (task->scsi.status == SPDK_SCSI_STATUS_GOOD)) { + if (data_len < transfer_len) { + /* underflow */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Underflow %zu/%u\n", data_len, transfer_len); + residual_len = transfer_len - data_len; + U_bit = 1; + } else if (data_len > transfer_len) { + /* overflow */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Overflow %zu/%u\n", data_len, transfer_len); + residual_len = data_len - transfer_len; + O_bit = 1; + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Transfer %u\n", transfer_len); + } + } + + /* response PDU */ + rsp_pdu = iscsi_get_pdu(conn); + assert(rsp_pdu != NULL); + rsph = (struct iscsi_bhs_scsi_resp *)&rsp_pdu->bhs; + assert(task->scsi.sense_data_len <= sizeof(rsp_pdu->sense.data)); + memcpy(rsp_pdu->sense.data, task->scsi.sense_data, task->scsi.sense_data_len); + to_be16(&rsp_pdu->sense.length, task->scsi.sense_data_len); + rsp_pdu->data = (uint8_t *)&rsp_pdu->sense; + rsp_pdu->data_from_mempool = true; + + /* + * we need to hold onto this task/cmd because until the + * PDU has been written out + */ + rsp_pdu->task = task; + task->scsi.ref++; + + rsph->opcode = ISCSI_OP_SCSI_RSP; + rsph->flags |= 0x80; /* bit 0 is default to 1 */ + + if (O_bit) { + rsph->flags |= ISCSI_SCSI_OVERFLOW; + } + + if (U_bit) { + rsph->flags |= ISCSI_SCSI_UNDERFLOW; + } + + rsph->status = task->scsi.status; + if (task->scsi.sense_data_len) { + /* SenseLength (2 bytes) + SenseData */ + DSET24(rsph->data_segment_len, 2 + task->scsi.sense_data_len); + } + to_be32(&rsph->itt, task_tag); + + to_be32(&rsph->stat_sn, conn->StatSN); + conn->StatSN++; + + if (!iscsi_task_is_immediate(primary)) { + conn->sess->MaxCmdSN++; + } + + to_be32(&rsph->exp_cmd_sn, conn->sess->ExpCmdSN); + to_be32(&rsph->max_cmd_sn, conn->sess->MaxCmdSN); + + to_be32(&rsph->bi_read_res_cnt, 0); + to_be32(&rsph->res_cnt, residual_len); + + iscsi_conn_write_pdu(conn, rsp_pdu, iscsi_conn_pdu_generic_complete, NULL); +} + +/* + * This function compare the input pdu's bhs with the pdu's bhs associated by + * active_r2t_tasks and queued_r2t_tasks in a connection + */ +static bool +iscsi_compare_pdu_bhs_within_existed_r2t_tasks(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *pdu) +{ + struct spdk_iscsi_task *task; + + TAILQ_FOREACH(task, &conn->active_r2t_tasks, link) { + if (!memcmp(&pdu->bhs, iscsi_task_get_bhs(task), ISCSI_BHS_LEN)) { + return true; + } + } + + TAILQ_FOREACH(task, &conn->queued_r2t_tasks, link) { + if (!memcmp(&pdu->bhs, iscsi_task_get_bhs(task), ISCSI_BHS_LEN)) { + return true; + } + } + + return false; +} + +void +iscsi_queue_task(struct spdk_iscsi_conn *conn, struct spdk_iscsi_task *task) +{ + spdk_trace_record(TRACE_ISCSI_TASK_QUEUE, conn->id, task->scsi.length, + (uintptr_t)task, (uintptr_t)task->pdu); + task->is_queued = true; + spdk_scsi_dev_queue_task(conn->dev, &task->scsi); +} + +static int +iscsi_pdu_payload_op_scsi_read(struct spdk_iscsi_conn *conn, struct spdk_iscsi_task *task) +{ + if (task->scsi.transfer_len <= SPDK_BDEV_LARGE_BUF_MAX_SIZE) { + task->parent = NULL; + task->scsi.offset = 0; + task->scsi.length = task->scsi.transfer_len; + spdk_scsi_task_set_data(&task->scsi, NULL, 0); + + iscsi_queue_task(conn, task); + return 0; + } else { + TAILQ_INIT(&task->subtask_list); + task->current_datain_offset = 0; + TAILQ_INSERT_TAIL(&conn->queued_datain_tasks, task, link); + + return iscsi_conn_handle_queued_datain_tasks(conn); + } +} + +static int +iscsi_pdu_payload_op_scsi_write(struct spdk_iscsi_conn *conn, struct spdk_iscsi_task *task) +{ + struct spdk_iscsi_pdu *pdu; + struct iscsi_bhs_scsi_req *reqh; + uint32_t transfer_len; + uint32_t scsi_data_len; + int rc; + + pdu = iscsi_task_get_pdu(task); + reqh = (struct iscsi_bhs_scsi_req *)&pdu->bhs; + + transfer_len = task->scsi.transfer_len; + + if (spdk_likely(!pdu->dif_insert_or_strip)) { + scsi_data_len = pdu->data_segment_len; + } else { + scsi_data_len = pdu->data_buf_len; + } + + if (reqh->final_bit && + pdu->data_segment_len < transfer_len) { + /* needs R2T */ + rc = add_transfer_task(conn, task); + if (rc < 0) { + SPDK_ERRLOG("add_transfer_task() failed\n"); + iscsi_task_put(task); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + /* Non-immediate writes */ + if (pdu->data_segment_len == 0) { + return 0; + } else { + /* we are doing the first partial write task */ + task->scsi.ref++; + spdk_scsi_task_set_data(&task->scsi, pdu->data, scsi_data_len); + task->scsi.length = pdu->data_segment_len; + } + } + + if (pdu->data_segment_len == transfer_len) { + /* we are doing small writes with no R2T */ + spdk_scsi_task_set_data(&task->scsi, pdu->data, scsi_data_len); + task->scsi.length = transfer_len; + } + + iscsi_queue_task(conn, task); + return 0; +} + +static int +iscsi_pdu_hdr_op_scsi(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct spdk_iscsi_task *task; + struct spdk_scsi_dev *dev; + uint8_t *cdb; + uint64_t lun; + uint32_t task_tag; + uint32_t transfer_len; + int R_bit, W_bit; + int lun_i; + struct iscsi_bhs_scsi_req *reqh; + + if (conn->sess->session_type != SESSION_TYPE_NORMAL) { + SPDK_ERRLOG("ISCSI_OP_SCSI not allowed in discovery and invalid session\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + reqh = (struct iscsi_bhs_scsi_req *)&pdu->bhs; + + R_bit = reqh->read_bit; + W_bit = reqh->write_bit; + lun = from_be64(&reqh->lun); + task_tag = from_be32(&reqh->itt); + transfer_len = from_be32(&reqh->expected_data_xfer_len); + cdb = reqh->cdb; + + SPDK_LOGDUMP(SPDK_LOG_ISCSI, "CDB", cdb, 16); + + task = iscsi_task_get(conn, NULL, iscsi_task_cpl); + if (!task) { + SPDK_ERRLOG("Unable to acquire task\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + iscsi_task_associate_pdu(task, pdu); + lun_i = spdk_scsi_lun_id_fmt_to_int(lun); + task->lun_id = lun_i; + dev = conn->dev; + task->scsi.lun = spdk_scsi_dev_get_lun(dev, lun_i); + + if ((R_bit != 0) && (W_bit != 0)) { + SPDK_ERRLOG("Bidirectional CDB is not supported\n"); + iscsi_task_put(task); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + task->scsi.cdb = cdb; + task->tag = task_tag; + task->scsi.transfer_len = transfer_len; + task->scsi.target_port = conn->target_port; + task->scsi.initiator_port = conn->initiator_port; + task->parent = NULL; + task->rsp_scsi_status = SPDK_SCSI_STATUS_GOOD; + + if (task->scsi.lun == NULL) { + spdk_scsi_task_process_null_lun(&task->scsi); + iscsi_task_cpl(&task->scsi); + return 0; + } + + /* no bi-directional support */ + if (R_bit) { + task->scsi.dxfer_dir = SPDK_SCSI_DIR_FROM_DEV; + } else if (W_bit) { + task->scsi.dxfer_dir = SPDK_SCSI_DIR_TO_DEV; + + if ((conn->sess->ErrorRecoveryLevel >= 1) && + (iscsi_compare_pdu_bhs_within_existed_r2t_tasks(conn, pdu))) { + iscsi_task_response(conn, task); + iscsi_task_put(task); + return 0; + } + + if (pdu->data_segment_len > iscsi_get_max_immediate_data_size()) { + SPDK_ERRLOG("data segment len(=%zu) > immediate data len(=%"PRIu32")\n", + pdu->data_segment_len, iscsi_get_max_immediate_data_size()); + iscsi_task_put(task); + return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + if (pdu->data_segment_len > transfer_len) { + SPDK_ERRLOG("data segment len(=%zu) > task transfer len(=%d)\n", + pdu->data_segment_len, transfer_len); + iscsi_task_put(task); + return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + /* check the ImmediateData and also pdu->data_segment_len */ + if ((!conn->sess->ImmediateData && (pdu->data_segment_len > 0)) || + (pdu->data_segment_len > conn->sess->FirstBurstLength)) { + iscsi_task_put(task); + return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + if (spdk_unlikely(spdk_scsi_lun_get_dif_ctx(task->scsi.lun, &task->scsi, &pdu->dif_ctx))) { + pdu->dif_insert_or_strip = true; + } + } else { + /* neither R nor W bit set */ + task->scsi.dxfer_dir = SPDK_SCSI_DIR_NONE; + if (transfer_len > 0) { + iscsi_task_put(task); + SPDK_ERRLOG("Reject scsi cmd with EDTL > 0 but (R | W) == 0\n"); + return iscsi_reject(conn, pdu, ISCSI_REASON_INVALID_PDU_FIELD); + } + } + + pdu->task = task; + return 0; +} + +static int +iscsi_pdu_payload_op_scsi(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct spdk_iscsi_task *task; + + if (pdu->task == NULL) { + return 0; + } + + task = pdu->task; + + if (spdk_scsi_dev_get_lun(conn->dev, task->lun_id) == NULL) { + spdk_scsi_task_process_null_lun(&task->scsi); + iscsi_task_cpl(&task->scsi); + return 0; + } + + switch (task->scsi.dxfer_dir) { + case SPDK_SCSI_DIR_FROM_DEV: + return iscsi_pdu_payload_op_scsi_read(conn, task); + case SPDK_SCSI_DIR_TO_DEV: + return iscsi_pdu_payload_op_scsi_write(conn, task); + case SPDK_SCSI_DIR_NONE: + iscsi_queue_task(conn, task); + return 0; + default: + assert(false); + iscsi_task_put(task); + break; + } + + return SPDK_ISCSI_CONNECTION_FATAL; +} + +static void +abort_transfer_task_in_task_mgmt_resp(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task) +{ + struct spdk_iscsi_pdu *pdu; + + pdu = iscsi_task_get_pdu(task); + + switch (task->scsi.function) { + /* abort task identified by Reference Task Tag field */ + case ISCSI_TASK_FUNC_ABORT_TASK: + iscsi_del_transfer_task(conn, task->scsi.abort_id); + break; + + /* abort all tasks issued via this session on the LUN */ + case ISCSI_TASK_FUNC_ABORT_TASK_SET: + iscsi_clear_all_transfer_task(conn, task->scsi.lun, pdu); + break; + + case ISCSI_TASK_FUNC_LOGICAL_UNIT_RESET: + iscsi_clear_all_transfer_task(conn, task->scsi.lun, pdu); + break; + } +} + +void +iscsi_task_mgmt_response(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_task_req *reqh; + struct iscsi_bhs_task_resp *rsph; + + if (task->pdu == NULL) { + /* + * This was an internally generated task management command, + * usually from LUN cleanup when a connection closes. + */ + return; + } + + reqh = (struct iscsi_bhs_task_req *)&task->pdu->bhs; + /* response PDU */ + rsp_pdu = iscsi_get_pdu(conn); + rsph = (struct iscsi_bhs_task_resp *)&rsp_pdu->bhs; + rsph->opcode = ISCSI_OP_TASK_RSP; + rsph->flags |= 0x80; /* bit 0 default to 1 */ + switch (task->scsi.response) { + case SPDK_SCSI_TASK_MGMT_RESP_COMPLETE: + abort_transfer_task_in_task_mgmt_resp(conn, task); + rsph->response = ISCSI_TASK_FUNC_RESP_COMPLETE; + break; + case SPDK_SCSI_TASK_MGMT_RESP_SUCCESS: + abort_transfer_task_in_task_mgmt_resp(conn, task); + rsph->response = ISCSI_TASK_FUNC_RESP_COMPLETE; + break; + case SPDK_SCSI_TASK_MGMT_RESP_REJECT: + rsph->response = ISCSI_TASK_FUNC_REJECTED; + break; + case SPDK_SCSI_TASK_MGMT_RESP_INVALID_LUN: + rsph->response = ISCSI_TASK_FUNC_RESP_LUN_NOT_EXIST; + break; + case SPDK_SCSI_TASK_MGMT_RESP_TARGET_FAILURE: + rsph->response = ISCSI_TASK_FUNC_REJECTED; + break; + case SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED: + rsph->response = ISCSI_TASK_FUNC_RESP_FUNC_NOT_SUPPORTED; + break; + } + rsph->itt = reqh->itt; + + to_be32(&rsph->stat_sn, conn->StatSN); + conn->StatSN++; + + if (reqh->immediate == 0) { + conn->sess->MaxCmdSN++; + } + + to_be32(&rsph->exp_cmd_sn, conn->sess->ExpCmdSN); + to_be32(&rsph->max_cmd_sn, conn->sess->MaxCmdSN); + + iscsi_conn_write_pdu(conn, rsp_pdu, iscsi_conn_pdu_generic_complete, NULL); +} + +static void +iscsi_queue_mgmt_task(struct spdk_iscsi_conn *conn, struct spdk_iscsi_task *task) +{ + struct spdk_scsi_lun *lun; + + lun = spdk_scsi_dev_get_lun(conn->dev, task->lun_id); + if (lun == NULL) { + task->scsi.response = SPDK_SCSI_TASK_MGMT_RESP_INVALID_LUN; + iscsi_task_mgmt_response(conn, task); + iscsi_task_put(task); + return; + } + + spdk_scsi_dev_queue_mgmt_task(conn->dev, &task->scsi); +} + +static int +_iscsi_op_abort_task(void *arg) +{ + struct spdk_iscsi_task *task = arg; + int rc; + + rc = iscsi_conn_abort_queued_datain_task(task->conn, task->scsi.abort_id); + if (rc != 0) { + return SPDK_POLLER_BUSY; + } + + spdk_poller_unregister(&task->mgmt_poller); + iscsi_queue_mgmt_task(task->conn, task); + return SPDK_POLLER_BUSY; +} + +static void +iscsi_op_abort_task(struct spdk_iscsi_task *task, uint32_t ref_task_tag) +{ + task->scsi.abort_id = ref_task_tag; + task->scsi.function = SPDK_SCSI_TASK_FUNC_ABORT_TASK; + task->mgmt_poller = SPDK_POLLER_REGISTER(_iscsi_op_abort_task, task, 10); +} + +static int +_iscsi_op_abort_task_set(void *arg) +{ + struct spdk_iscsi_task *task = arg; + int rc; + + rc = iscsi_conn_abort_queued_datain_tasks(task->conn, task->scsi.lun, + task->pdu); + if (rc != 0) { + return SPDK_POLLER_BUSY; + } + + spdk_poller_unregister(&task->mgmt_poller); + iscsi_queue_mgmt_task(task->conn, task); + return SPDK_POLLER_BUSY; +} + +void +iscsi_op_abort_task_set(struct spdk_iscsi_task *task, uint8_t function) +{ + task->scsi.function = function; + task->mgmt_poller = SPDK_POLLER_REGISTER(_iscsi_op_abort_task_set, task, 10); +} + +static int +iscsi_pdu_hdr_op_task(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct iscsi_bhs_task_req *reqh; + uint64_t lun; + uint32_t task_tag; + uint32_t ref_task_tag; + uint8_t function; + int lun_i; + struct spdk_iscsi_task *task; + struct spdk_scsi_dev *dev; + + if (conn->sess->session_type != SESSION_TYPE_NORMAL) { + SPDK_ERRLOG("ISCSI_OP_TASK not allowed in discovery and invalid session\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + reqh = (struct iscsi_bhs_task_req *)&pdu->bhs; + function = reqh->flags & ISCSI_TASK_FUNCTION_MASK; + lun = from_be64(&reqh->lun); + task_tag = from_be32(&reqh->itt); + ref_task_tag = from_be32(&reqh->ref_task_tag); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "I=%d, func=%d, ITT=%x, ref TT=%x, LUN=0x%16.16"PRIx64"\n", + reqh->immediate, function, task_tag, ref_task_tag, lun); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "StatSN=%u, ExpCmdSN=%u, MaxCmdSN=%u\n", + conn->StatSN, conn->sess->ExpCmdSN, conn->sess->MaxCmdSN); + + lun_i = spdk_scsi_lun_id_fmt_to_int(lun); + dev = conn->dev; + + task = iscsi_task_get(conn, NULL, iscsi_task_mgmt_cpl); + if (!task) { + SPDK_ERRLOG("Unable to acquire task\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + iscsi_task_associate_pdu(task, pdu); + task->scsi.target_port = conn->target_port; + task->scsi.initiator_port = conn->initiator_port; + task->tag = task_tag; + task->scsi.lun = spdk_scsi_dev_get_lun(dev, lun_i); + task->lun_id = lun_i; + + if (task->scsi.lun == NULL) { + task->scsi.response = SPDK_SCSI_TASK_MGMT_RESP_INVALID_LUN; + iscsi_task_mgmt_response(conn, task); + iscsi_task_put(task); + return 0; + } + + switch (function) { + /* abort task identified by Referenced Task Tag field */ + case ISCSI_TASK_FUNC_ABORT_TASK: + SPDK_NOTICELOG("ABORT_TASK\n"); + + iscsi_op_abort_task(task, ref_task_tag); + return 0; + + /* abort all tasks issued via this session on the LUN */ + case ISCSI_TASK_FUNC_ABORT_TASK_SET: + SPDK_NOTICELOG("ABORT_TASK_SET\n"); + + iscsi_op_abort_task_set(task, SPDK_SCSI_TASK_FUNC_ABORT_TASK_SET); + return 0; + + case ISCSI_TASK_FUNC_CLEAR_TASK_SET: + task->scsi.response = SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED; + SPDK_NOTICELOG("CLEAR_TASK_SET (Unsupported)\n"); + break; + + case ISCSI_TASK_FUNC_CLEAR_ACA: + task->scsi.response = SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED; + SPDK_NOTICELOG("CLEAR_ACA (Unsupported)\n"); + break; + + case ISCSI_TASK_FUNC_LOGICAL_UNIT_RESET: + SPDK_NOTICELOG("LOGICAL_UNIT_RESET\n"); + + iscsi_op_abort_task_set(task, SPDK_SCSI_TASK_FUNC_LUN_RESET); + return 0; + + case ISCSI_TASK_FUNC_TARGET_WARM_RESET: + SPDK_NOTICELOG("TARGET_WARM_RESET (Unsupported)\n"); + task->scsi.response = SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED; + break; + + case ISCSI_TASK_FUNC_TARGET_COLD_RESET: + SPDK_NOTICELOG("TARGET_COLD_RESET (Unsupported)\n"); + task->scsi.response = SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED; + break; + + case ISCSI_TASK_FUNC_TASK_REASSIGN: + SPDK_NOTICELOG("TASK_REASSIGN (Unsupported)\n"); + task->scsi.response = SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED; + break; + + default: + SPDK_ERRLOG("unsupported function %d\n", function); + task->scsi.response = SPDK_SCSI_TASK_MGMT_RESP_REJECT; + break; + } + + iscsi_task_mgmt_response(conn, task); + iscsi_task_put(task); + return 0; +} + +static int +iscsi_pdu_hdr_op_nopout(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct iscsi_bhs_nop_out *reqh; + uint32_t task_tag; + uint32_t transfer_tag; + int I_bit; + + if (conn->sess->session_type == SESSION_TYPE_DISCOVERY) { + SPDK_ERRLOG("ISCSI_OP_NOPOUT not allowed in discovery session\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + reqh = (struct iscsi_bhs_nop_out *)&pdu->bhs; + I_bit = reqh->immediate; + + if (pdu->data_segment_len > SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH) { + return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + task_tag = from_be32(&reqh->itt); + transfer_tag = from_be32(&reqh->ttt); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "I=%d, ITT=%x, TTT=%x\n", + I_bit, task_tag, transfer_tag); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "CmdSN=%u, StatSN=%u, ExpCmdSN=%u, MaxCmdSN=%u\n", + pdu->cmd_sn, conn->StatSN, conn->sess->ExpCmdSN, + conn->sess->MaxCmdSN); + + if (transfer_tag != 0xFFFFFFFF && transfer_tag != (uint32_t)conn->id) { + SPDK_ERRLOG("invalid transfer tag 0x%x\n", transfer_tag); + /* + * Technically we should probably fail the connection here, but for now + * just print the error message and continue. + */ + } + + if (task_tag == 0xffffffffU && I_bit == 0) { + SPDK_ERRLOG("got NOPOUT ITT=0xffffffff, I=0\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + return 0; +} + +static int +iscsi_pdu_payload_op_nopout(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_bhs_nop_out *reqh; + struct iscsi_bhs_nop_in *rsph; + uint8_t *data; + uint64_t lun; + uint32_t task_tag; + int I_bit; + int data_len; + + reqh = (struct iscsi_bhs_nop_out *)&pdu->bhs; + I_bit = reqh->immediate; + + data_len = pdu->data_segment_len; + if (data_len > conn->MaxRecvDataSegmentLength) { + data_len = conn->MaxRecvDataSegmentLength; + } + + lun = from_be64(&reqh->lun); + task_tag = from_be32(&reqh->itt); + + /* + * We don't actually check to see if this is a response to the NOP-In + * that we sent. Our goal is to just verify that the initiator is + * alive and responding to commands, not to verify that it tags + * NOP-Outs correctly + */ + conn->nop_outstanding = false; + + if (task_tag == 0xffffffffU) { + assert(I_bit == 1); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "got NOPOUT ITT=0xffffffff\n"); + return 0; + } + + data = calloc(1, data_len); + if (!data) { + SPDK_ERRLOG("calloc() failed for ping data\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + /* response of NOPOUT */ + if (data_len > 0) { + /* copy ping data */ + memcpy(data, pdu->data, data_len); + } + + /* response PDU */ + rsp_pdu = iscsi_get_pdu(conn); + assert(rsp_pdu != NULL); + + rsph = (struct iscsi_bhs_nop_in *)&rsp_pdu->bhs; + rsp_pdu->data = data; + rsph->opcode = ISCSI_OP_NOPIN; + rsph->flags |= 0x80; /* bit 0 default to 1 */ + DSET24(rsph->data_segment_len, data_len); + to_be64(&rsph->lun, lun); + to_be32(&rsph->itt, task_tag); + to_be32(&rsph->ttt, 0xffffffffU); + + to_be32(&rsph->stat_sn, conn->StatSN); + conn->StatSN++; + + if (I_bit == 0) { + conn->sess->MaxCmdSN++; + } + + to_be32(&rsph->exp_cmd_sn, conn->sess->ExpCmdSN); + to_be32(&rsph->max_cmd_sn, conn->sess->MaxCmdSN); + + iscsi_conn_write_pdu(conn, rsp_pdu, iscsi_conn_pdu_generic_complete, NULL); + conn->last_nopin = spdk_get_ticks(); + + return 0; +} + +/* This function returns the spdk_scsi_task by searching the snack list via + * task transfertag and the pdu's opcode + */ +static struct spdk_iscsi_task * +get_scsi_task_from_ttt(struct spdk_iscsi_conn *conn, uint32_t transfer_tag) +{ + struct spdk_iscsi_pdu *pdu; + struct iscsi_bhs_data_in *datain_bhs; + + TAILQ_FOREACH(pdu, &conn->snack_pdu_list, tailq) { + if (pdu->bhs.opcode == ISCSI_OP_SCSI_DATAIN) { + datain_bhs = (struct iscsi_bhs_data_in *)&pdu->bhs; + if (from_be32(&datain_bhs->ttt) == transfer_tag) { + return pdu->task; + } + } + } + + return NULL; +} + +/* This function returns the spdk_scsi_task by searching the snack list via + * initiator task tag and the pdu's opcode + */ +static struct spdk_iscsi_task * +get_scsi_task_from_itt(struct spdk_iscsi_conn *conn, + uint32_t task_tag, enum iscsi_op opcode) +{ + struct spdk_iscsi_pdu *pdu; + + TAILQ_FOREACH(pdu, &conn->snack_pdu_list, tailq) { + if (pdu->bhs.opcode == opcode && + pdu->task != NULL && + pdu->task->tag == task_tag) { + return pdu->task; + } + } + + return NULL; +} + +/* This function is used to handle the r2t snack */ +static int +iscsi_handle_r2t_snack(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task, + struct spdk_iscsi_pdu *pdu, uint32_t beg_run, + uint32_t run_length, int32_t task_tag) +{ + int32_t last_r2tsn; + int i; + + if (beg_run < task->acked_r2tsn) { + SPDK_ERRLOG("ITT: 0x%08x, R2T SNACK requests retransmission of" + "R2TSN: from 0x%08x to 0x%08x. But it has already" + "ack to R2TSN:0x%08x, protocol error.\n", + task_tag, beg_run, (beg_run + run_length), + (task->acked_r2tsn - 1)); + return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + if (run_length) { + if ((beg_run + run_length) > task->R2TSN) { + SPDK_ERRLOG("ITT: 0x%08x, received R2T SNACK with" + "BegRun: 0x%08x, RunLength: 0x%08x, exceeds" + "current R2TSN: 0x%08x, protocol error.\n", + task_tag, beg_run, run_length, + task->R2TSN); + + return iscsi_reject(conn, pdu, ISCSI_REASON_INVALID_PDU_FIELD); + } + last_r2tsn = (beg_run + run_length); + } else { + last_r2tsn = task->R2TSN; + } + + for (i = beg_run; i < last_r2tsn; i++) { + if (iscsi_send_r2t_recovery(conn, task, i, false) < 0) { + SPDK_ERRLOG("The r2t_sn=%d of r2t_task=%p is not sent\n", i, task); + } + } + return 0; +} + +/* This function is used to recover the data in packet */ +static int +iscsi_handle_recovery_datain(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task, + struct spdk_iscsi_pdu *pdu, uint32_t beg_run, + uint32_t run_length, uint32_t task_tag) +{ + struct spdk_iscsi_pdu *old_pdu, *pdu_temp; + uint32_t i; + struct iscsi_bhs_data_in *datain_header; + uint32_t last_statsn; + + task = iscsi_task_get_primary(task); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "iscsi_handle_recovery_datain\n"); + + if (beg_run < task->acked_data_sn) { + SPDK_ERRLOG("ITT: 0x%08x, DATA IN SNACK requests retransmission of" + "DATASN: from 0x%08x to 0x%08x but already acked to " + "DATASN: 0x%08x protocol error\n", + task_tag, beg_run, + (beg_run + run_length), (task->acked_data_sn - 1)); + + return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + if (run_length == 0) { + /* as the DataSN begins at 0 */ + run_length = task->datain_datasn + 1; + } + + if ((beg_run + run_length - 1) > task->datain_datasn) { + SPDK_ERRLOG("Initiator requests BegRun: 0x%08x, RunLength:" + "0x%08x greater than maximum DataSN: 0x%08x.\n", + beg_run, run_length, task->datain_datasn); + + return -1; + } else { + last_statsn = beg_run + run_length - 1; + } + + for (i = beg_run; i <= last_statsn; i++) { + TAILQ_FOREACH_SAFE(old_pdu, &conn->snack_pdu_list, tailq, pdu_temp) { + if (old_pdu->bhs.opcode == ISCSI_OP_SCSI_DATAIN) { + datain_header = (struct iscsi_bhs_data_in *)&old_pdu->bhs; + if (from_be32(&datain_header->itt) == task_tag && + from_be32(&datain_header->data_sn) == i) { + TAILQ_REMOVE(&conn->snack_pdu_list, old_pdu, tailq); + iscsi_conn_write_pdu(conn, old_pdu, old_pdu->cb_fn, old_pdu->cb_arg); + break; + } + } + } + } + return 0; +} + +/* This function is used to handle the status snack */ +static int +iscsi_handle_status_snack(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + uint32_t beg_run; + uint32_t run_length; + struct iscsi_bhs_snack_req *reqh; + uint32_t i; + uint32_t last_statsn; + bool found_pdu; + struct spdk_iscsi_pdu *old_pdu; + + reqh = (struct iscsi_bhs_snack_req *)&pdu->bhs; + beg_run = from_be32(&reqh->beg_run); + run_length = from_be32(&reqh->run_len); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "beg_run=%d, run_length=%d, conn->StatSN=" + "%d, conn->exp_statsn=%d\n", beg_run, run_length, + conn->StatSN, conn->exp_statsn); + + if (!beg_run) { + beg_run = conn->exp_statsn; + } else if (beg_run < conn->exp_statsn) { + SPDK_ERRLOG("Got Status SNACK Begrun: 0x%08x, RunLength: 0x%08x " + "but already got ExpStatSN: 0x%08x on CID:%hu.\n", + beg_run, run_length, conn->StatSN, conn->cid); + + return iscsi_reject(conn, pdu, ISCSI_REASON_INVALID_PDU_FIELD); + } + + last_statsn = (!run_length) ? conn->StatSN : (beg_run + run_length); + + for (i = beg_run; i < last_statsn; i++) { + found_pdu = false; + TAILQ_FOREACH(old_pdu, &conn->snack_pdu_list, tailq) { + if (from_be32(&old_pdu->bhs.stat_sn) == i) { + found_pdu = true; + break; + } + } + + if (!found_pdu) { + SPDK_ERRLOG("Unable to find StatSN: 0x%08x. For a Status" + "SNACK, assuming this is a proactive SNACK " + "for an untransmitted StatSN, ignoring.\n", + beg_run); + } else { + TAILQ_REMOVE(&conn->snack_pdu_list, old_pdu, tailq); + iscsi_conn_write_pdu(conn, old_pdu, old_pdu->cb_fn, old_pdu->cb_arg); + } + } + + return 0; +} + +/* This function is used to handle the data ack snack */ +static int +iscsi_handle_data_ack(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + uint32_t transfer_tag; + uint32_t beg_run; + uint32_t run_length; + struct spdk_iscsi_pdu *old_pdu; + uint32_t old_datasn; + struct iscsi_bhs_snack_req *reqh; + struct spdk_iscsi_task *task; + struct iscsi_bhs_data_in *datain_header; + struct spdk_iscsi_task *primary; + + reqh = (struct iscsi_bhs_snack_req *)&pdu->bhs; + transfer_tag = from_be32(&reqh->ttt); + beg_run = from_be32(&reqh->beg_run); + run_length = from_be32(&reqh->run_len); + task = NULL; + datain_header = NULL; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "beg_run=%d,transfer_tag=%d,run_len=%d\n", + beg_run, transfer_tag, run_length); + + task = get_scsi_task_from_ttt(conn, transfer_tag); + if (!task) { + SPDK_ERRLOG("Data ACK SNACK for TTT: 0x%08x is invalid.\n", + transfer_tag); + goto reject_return; + } + + primary = iscsi_task_get_primary(task); + if ((run_length != 0) || (beg_run < primary->acked_data_sn)) { + SPDK_ERRLOG("TTT: 0x%08x Data ACK SNACK BegRUN: %d is less than " + "the next expected acked DataSN: %d\n", + transfer_tag, beg_run, primary->acked_data_sn); + goto reject_return; + } + + primary->acked_data_sn = beg_run; + + /* To free the pdu */ + TAILQ_FOREACH(old_pdu, &conn->snack_pdu_list, tailq) { + if (old_pdu->bhs.opcode == ISCSI_OP_SCSI_DATAIN) { + datain_header = (struct iscsi_bhs_data_in *) &old_pdu->bhs; + old_datasn = from_be32(&datain_header->data_sn); + if ((from_be32(&datain_header->ttt) == transfer_tag) && + (old_datasn == beg_run - 1)) { + TAILQ_REMOVE(&conn->snack_pdu_list, old_pdu, tailq); + iscsi_conn_free_pdu(conn, old_pdu); + break; + } + } + } + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Received Data ACK SNACK for TTT: 0x%08x," + " updated acked DataSN to 0x%08x.\n", transfer_tag, + (task->acked_data_sn - 1)); + + return 0; + +reject_return: + return iscsi_reject(conn, pdu, ISCSI_REASON_INVALID_SNACK); +} + +/* This function is used to handle the snack request from the initiator */ +static int +iscsi_pdu_hdr_op_snack(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct iscsi_bhs_snack_req *reqh; + struct spdk_iscsi_task *task; + int type; + uint32_t task_tag; + uint32_t beg_run; + uint32_t run_length; + int rc; + + if (conn->sess->session_type == SESSION_TYPE_DISCOVERY) { + SPDK_ERRLOG("ISCSI_OP_SNACK not allowed in discovery session\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + reqh = (struct iscsi_bhs_snack_req *)&pdu->bhs; + if (!conn->sess->ErrorRecoveryLevel) { + SPDK_ERRLOG("Got a SNACK request in ErrorRecoveryLevel=0\n"); + return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + type = reqh->flags & ISCSI_FLAG_SNACK_TYPE_MASK; + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "The value of type is %d\n", type); + + switch (type) { + case 0: + reqh = (struct iscsi_bhs_snack_req *)&pdu->bhs; + task_tag = from_be32(&reqh->itt); + beg_run = from_be32(&reqh->beg_run); + run_length = from_be32(&reqh->run_len); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "beg_run=%d, run_length=%d, " + "task_tag=%x, transfer_tag=%u\n", beg_run, + run_length, task_tag, from_be32(&reqh->ttt)); + + task = get_scsi_task_from_itt(conn, task_tag, + ISCSI_OP_SCSI_DATAIN); + if (task) { + return iscsi_handle_recovery_datain(conn, task, pdu, + beg_run, run_length, task_tag); + } + task = get_scsi_task_from_itt(conn, task_tag, ISCSI_OP_R2T); + if (task) { + return iscsi_handle_r2t_snack(conn, task, pdu, beg_run, + run_length, task_tag); + } + SPDK_ERRLOG("It is Neither datain nor r2t recovery request\n"); + rc = -1; + break; + case ISCSI_FLAG_SNACK_TYPE_STATUS: + rc = iscsi_handle_status_snack(conn, pdu); + break; + case ISCSI_FLAG_SNACK_TYPE_DATA_ACK: + rc = iscsi_handle_data_ack(conn, pdu); + break; + case ISCSI_FLAG_SNACK_TYPE_RDATA: + SPDK_ERRLOG("R-Data SNACK is Not Supported int spdk\n"); + rc = iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + break; + default: + SPDK_ERRLOG("Unknown SNACK type %d, protocol error\n", type); + rc = iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + break; + } + + return rc; +} + +static int +iscsi_pdu_hdr_op_data(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct spdk_iscsi_task *task, *subtask; + struct iscsi_bhs_data_out *reqh; + struct spdk_scsi_lun *lun_dev; + uint32_t transfer_tag; + uint32_t task_tag; + uint32_t transfer_len; + uint32_t DataSN; + uint32_t buffer_offset; + uint32_t len; + int F_bit; + int rc; + int reject_reason = ISCSI_REASON_INVALID_PDU_FIELD; + + if (conn->sess->session_type == SESSION_TYPE_DISCOVERY) { + SPDK_ERRLOG("ISCSI_OP_SCSI_DATAOUT not allowed in discovery session\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + reqh = (struct iscsi_bhs_data_out *)&pdu->bhs; + F_bit = !!(reqh->flags & ISCSI_FLAG_FINAL); + transfer_tag = from_be32(&reqh->ttt); + task_tag = from_be32(&reqh->itt); + DataSN = from_be32(&reqh->data_sn); + buffer_offset = from_be32(&reqh->buffer_offset); + + if (pdu->data_segment_len > SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH) { + reject_reason = ISCSI_REASON_PROTOCOL_ERROR; + goto reject_return; + } + + task = get_transfer_task(conn, transfer_tag); + if (task == NULL) { + SPDK_ERRLOG("Not found task for transfer_tag=%x\n", transfer_tag); + goto reject_return; + } + + lun_dev = spdk_scsi_dev_get_lun(conn->dev, task->lun_id); + + if (pdu->data_segment_len > task->desired_data_transfer_length) { + SPDK_ERRLOG("the dataout pdu data length is larger than the value sent by R2T PDU\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + if (task->tag != task_tag) { + SPDK_ERRLOG("The r2t task tag is %u, and the dataout task tag is %u\n", + task->tag, task_tag); + goto reject_return; + } + + if (DataSN != task->r2t_datasn) { + SPDK_ERRLOG("DataSN(%u) exp=%d error\n", DataSN, task->r2t_datasn); + if (conn->sess->ErrorRecoveryLevel >= 1) { + goto send_r2t_recovery_return; + } else { + reject_reason = ISCSI_REASON_PROTOCOL_ERROR; + goto reject_return; + } + } + + if (buffer_offset != task->next_expected_r2t_offset) { + SPDK_ERRLOG("offset(%u) error\n", buffer_offset); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + transfer_len = task->scsi.transfer_len; + task->current_r2t_length += pdu->data_segment_len; + task->next_expected_r2t_offset += pdu->data_segment_len; + task->r2t_datasn++; + + if (task->current_r2t_length > conn->sess->MaxBurstLength) { + SPDK_ERRLOG("R2T burst(%u) > MaxBurstLength(%u)\n", + task->current_r2t_length, + conn->sess->MaxBurstLength); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + if (F_bit) { + /* + * This R2T burst is done. Clear the length before we + * receive a PDU for the next R2t burst. + */ + task->current_r2t_length = 0; + } + + subtask = iscsi_task_get(conn, task, iscsi_task_cpl); + if (subtask == NULL) { + SPDK_ERRLOG("Unable to acquire subtask\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + subtask->scsi.offset = buffer_offset; + subtask->scsi.length = pdu->data_segment_len; + iscsi_task_associate_pdu(subtask, pdu); + + if (task->next_expected_r2t_offset == transfer_len) { + task->acked_r2tsn++; + } else if (F_bit && (task->next_r2t_offset < transfer_len)) { + task->acked_r2tsn++; + len = spdk_min(conn->sess->MaxBurstLength, + (transfer_len - task->next_r2t_offset)); + rc = iscsi_send_r2t(conn, task, task->next_r2t_offset, len, + task->ttt, &task->R2TSN); + if (rc < 0) { + SPDK_ERRLOG("iscsi_send_r2t() failed\n"); + } + task->next_r2t_offset += len; + } + + if (lun_dev == NULL) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "LUN %d is removed, complete the task immediately\n", + task->lun_id); + subtask->scsi.transfer_len = subtask->scsi.length; + spdk_scsi_task_process_null_lun(&subtask->scsi); + iscsi_task_cpl(&subtask->scsi); + return 0; + } + + if (spdk_unlikely(spdk_scsi_lun_get_dif_ctx(lun_dev, &subtask->scsi, &pdu->dif_ctx))) { + pdu->dif_insert_or_strip = true; + } + + pdu->task = subtask; + return 0; + +send_r2t_recovery_return: + rc = iscsi_send_r2t_recovery(conn, task, task->acked_r2tsn, true); + if (rc == 0) { + return 0; + } + +reject_return: + return iscsi_reject(conn, pdu, reject_reason); +} + +static int +iscsi_pdu_payload_op_data(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct spdk_iscsi_task *subtask; + struct iscsi_bhs_data_out *reqh; + uint32_t transfer_tag; + + if (pdu->task == NULL) { + return 0; + } + + subtask = pdu->task; + + reqh = (struct iscsi_bhs_data_out *)&pdu->bhs; + transfer_tag = from_be32(&reqh->ttt); + + if (get_transfer_task(conn, transfer_tag) == NULL) { + SPDK_ERRLOG("Not found for transfer_tag=%x\n", transfer_tag); + subtask->scsi.transfer_len = subtask->scsi.length; + spdk_scsi_task_process_abort(&subtask->scsi); + iscsi_task_cpl(&subtask->scsi); + return 0; + } + + if (spdk_likely(!pdu->dif_insert_or_strip)) { + spdk_scsi_task_set_data(&subtask->scsi, pdu->data, pdu->data_segment_len); + } else { + spdk_scsi_task_set_data(&subtask->scsi, pdu->data, pdu->data_buf_len); + } + + if (spdk_scsi_dev_get_lun(conn->dev, subtask->lun_id) == NULL) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "LUN %d is removed, complete the task immediately\n", + subtask->lun_id); + subtask->scsi.transfer_len = subtask->scsi.length; + spdk_scsi_task_process_null_lun(&subtask->scsi); + iscsi_task_cpl(&subtask->scsi); + return 0; + } + + iscsi_queue_task(conn, subtask); + return 0; +} + +static void +init_login_reject_response(struct spdk_iscsi_pdu *pdu, struct spdk_iscsi_pdu *rsp_pdu) +{ + struct iscsi_bhs_login_rsp *rsph; + + memset(rsp_pdu, 0, sizeof(struct spdk_iscsi_pdu)); + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + rsph->version_max = ISCSI_VERSION; + rsph->version_act = ISCSI_VERSION; + rsph->opcode = ISCSI_OP_LOGIN_RSP; + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_INVALID_LOGIN_REQUEST; + rsph->itt = pdu->bhs.itt; +} + +static void +iscsi_pdu_dump(struct spdk_iscsi_pdu *pdu) +{ + SPDK_ERRLOGDUMP("PDU", (uint8_t *)&pdu->bhs, ISCSI_BHS_LEN); +} + +/* This function is used to refree the pdu when it is acknowledged */ +static void +remove_acked_pdu(struct spdk_iscsi_conn *conn, uint32_t ExpStatSN) +{ + struct spdk_iscsi_pdu *pdu, *pdu_temp; + uint32_t stat_sn; + + conn->exp_statsn = spdk_min(ExpStatSN, conn->StatSN); + TAILQ_FOREACH_SAFE(pdu, &conn->snack_pdu_list, tailq, pdu_temp) { + stat_sn = from_be32(&pdu->bhs.stat_sn); + if (spdk_sn32_lt(stat_sn, conn->exp_statsn)) { + TAILQ_REMOVE(&conn->snack_pdu_list, pdu, tailq); + iscsi_conn_free_pdu(conn, pdu); + } + } +} + +static int +iscsi_update_cmdsn(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + int opcode; + uint32_t ExpStatSN; + int I_bit; + struct spdk_iscsi_sess *sess; + struct iscsi_bhs_scsi_req *reqh; + + sess = conn->sess; + if (!sess) { + SPDK_ERRLOG("Connection has no associated session!\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + opcode = pdu->bhs.opcode; + reqh = (struct iscsi_bhs_scsi_req *)&pdu->bhs; + + pdu->cmd_sn = from_be32(&reqh->cmd_sn); + + I_bit = reqh->immediate; + if (I_bit == 0) { + if (spdk_sn32_lt(pdu->cmd_sn, sess->ExpCmdSN) || + spdk_sn32_gt(pdu->cmd_sn, sess->MaxCmdSN)) { + if (sess->session_type == SESSION_TYPE_NORMAL && + opcode != ISCSI_OP_SCSI_DATAOUT) { + SPDK_ERRLOG("CmdSN(%u) ignore (ExpCmdSN=%u, MaxCmdSN=%u)\n", + pdu->cmd_sn, sess->ExpCmdSN, sess->MaxCmdSN); + + if (sess->ErrorRecoveryLevel >= 1) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Skip the error in ERL 1 and 2\n"); + } else { + return SPDK_PDU_FATAL; + } + } + } + } else if (pdu->cmd_sn != sess->ExpCmdSN) { + SPDK_ERRLOG("CmdSN(%u) error ExpCmdSN=%u\n", pdu->cmd_sn, sess->ExpCmdSN); + + if (sess->ErrorRecoveryLevel >= 1) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Skip the error in ERL 1 and 2\n"); + } else if (opcode != ISCSI_OP_NOPOUT) { + /* + * The Linux initiator does not send valid CmdSNs for + * nopout under heavy load, so do not close the + * connection in that case. + */ + return SPDK_ISCSI_CONNECTION_FATAL; + } + } + + ExpStatSN = from_be32(&reqh->exp_stat_sn); + if (spdk_sn32_gt(ExpStatSN, conn->StatSN)) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "StatSN(%u) advanced\n", ExpStatSN); + ExpStatSN = conn->StatSN; + } + + if (sess->ErrorRecoveryLevel >= 1) { + remove_acked_pdu(conn, ExpStatSN); + } + + if (!I_bit && opcode != ISCSI_OP_SCSI_DATAOUT) { + sess->ExpCmdSN++; + } + + return 0; +} + +static int +iscsi_pdu_hdr_handle(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + int opcode; + int rc; + struct spdk_iscsi_pdu *rsp_pdu = NULL; + + if (pdu == NULL) { + return -1; + } + + opcode = pdu->bhs.opcode; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "opcode %x\n", opcode); + + if (opcode == ISCSI_OP_LOGIN) { + return iscsi_pdu_hdr_op_login(conn, pdu); + } + + /* connection in login phase but receive non-login opcode + * return response code 0x020b to initiator. + * */ + if (!conn->full_feature && conn->state == ISCSI_CONN_STATE_RUNNING) { + rsp_pdu = iscsi_get_pdu(conn); + if (rsp_pdu == NULL) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + init_login_reject_response(pdu, rsp_pdu); + iscsi_conn_write_pdu(conn, rsp_pdu, iscsi_conn_pdu_generic_complete, NULL); + SPDK_ERRLOG("Received opcode %d in login phase\n", opcode); + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } else if (conn->state == ISCSI_CONN_STATE_INVALID) { + SPDK_ERRLOG("before Full Feature\n"); + iscsi_pdu_dump(pdu); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + rc = iscsi_update_cmdsn(conn, pdu); + if (rc != 0) { + return rc; + } + + switch (opcode) { + case ISCSI_OP_NOPOUT: + rc = iscsi_pdu_hdr_op_nopout(conn, pdu); + break; + + case ISCSI_OP_SCSI: + rc = iscsi_pdu_hdr_op_scsi(conn, pdu); + break; + case ISCSI_OP_TASK: + rc = iscsi_pdu_hdr_op_task(conn, pdu); + break; + + case ISCSI_OP_TEXT: + rc = iscsi_pdu_hdr_op_text(conn, pdu); + break; + + case ISCSI_OP_LOGOUT: + rc = iscsi_pdu_hdr_op_logout(conn, pdu); + break; + + case ISCSI_OP_SCSI_DATAOUT: + rc = iscsi_pdu_hdr_op_data(conn, pdu); + break; + + case ISCSI_OP_SNACK: + rc = iscsi_pdu_hdr_op_snack(conn, pdu); + break; + + default: + SPDK_ERRLOG("unsupported opcode %x\n", opcode); + return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + if (rc < 0) { + SPDK_ERRLOG("processing PDU header (opcode=%x) failed on %s(%s)\n", + opcode, + conn->target_port != NULL ? spdk_scsi_port_get_name(conn->target_port) : "NULL", + conn->initiator_port != NULL ? spdk_scsi_port_get_name(conn->initiator_port) : "NULL"); + } + + return rc; +} + +static int +iscsi_pdu_payload_handle(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + int opcode; + int rc = 0; + + opcode = pdu->bhs.opcode; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "opcode %x\n", opcode); + + switch (opcode) { + case ISCSI_OP_LOGIN: + rc = iscsi_pdu_payload_op_login(conn, pdu); + break; + case ISCSI_OP_NOPOUT: + rc = iscsi_pdu_payload_op_nopout(conn, pdu); + break; + case ISCSI_OP_SCSI: + rc = iscsi_pdu_payload_op_scsi(conn, pdu); + break; + case ISCSI_OP_TASK: + break; + case ISCSI_OP_TEXT: + rc = iscsi_pdu_payload_op_text(conn, pdu); + break; + case ISCSI_OP_LOGOUT: + break; + case ISCSI_OP_SCSI_DATAOUT: + rc = iscsi_pdu_payload_op_data(conn, pdu); + break; + case ISCSI_OP_SNACK: + break; + default: + SPDK_ERRLOG("unsupported opcode %x\n", opcode); + return iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + if (rc < 0) { + SPDK_ERRLOG("processing PDU payload (opcode=%x) failed on %s(%s)\n", + opcode, + conn->target_port != NULL ? spdk_scsi_port_get_name(conn->target_port) : "NULL", + conn->initiator_port != NULL ? spdk_scsi_port_get_name(conn->initiator_port) : "NULL"); + } + + return rc; +} + +static int +iscsi_read_pdu(struct spdk_iscsi_conn *conn) +{ + enum iscsi_pdu_recv_state prev_state; + struct spdk_iscsi_pdu *pdu; + struct spdk_mempool *pool; + uint32_t crc32c; + int ahs_len; + uint32_t data_len; + int rc; + + do { + prev_state = conn->pdu_recv_state; + pdu = conn->pdu_in_progress; + + switch (conn->pdu_recv_state) { + case ISCSI_PDU_RECV_STATE_AWAIT_PDU_READY: + assert(conn->pdu_in_progress == NULL); + + conn->pdu_in_progress = iscsi_get_pdu(conn); + if (conn->pdu_in_progress == NULL) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_AWAIT_PDU_HDR; + break; + case ISCSI_PDU_RECV_STATE_AWAIT_PDU_HDR: + if (pdu->bhs_valid_bytes < ISCSI_BHS_LEN) { + rc = iscsi_conn_read_data(conn, + ISCSI_BHS_LEN - pdu->bhs_valid_bytes, + (uint8_t *)&pdu->bhs + pdu->bhs_valid_bytes); + if (rc < 0) { + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + pdu->bhs_valid_bytes += rc; + if (pdu->bhs_valid_bytes < ISCSI_BHS_LEN) { + return 0; + } + } + + pdu->data_segment_len = ISCSI_ALIGN(DGET24(pdu->bhs.data_segment_len)); + + /* AHS */ + ahs_len = pdu->bhs.total_ahs_len * 4; + assert(ahs_len <= ISCSI_AHS_LEN); + if (pdu->ahs_valid_bytes < ahs_len) { + rc = iscsi_conn_read_data(conn, + ahs_len - pdu->ahs_valid_bytes, + pdu->ahs + pdu->ahs_valid_bytes); + if (rc < 0) { + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + + pdu->ahs_valid_bytes += rc; + if (pdu->ahs_valid_bytes < ahs_len) { + return 0; + } + } + + /* Header Digest */ + if (conn->header_digest && + pdu->hdigest_valid_bytes < ISCSI_DIGEST_LEN) { + rc = iscsi_conn_read_data(conn, + ISCSI_DIGEST_LEN - pdu->hdigest_valid_bytes, + pdu->header_digest + pdu->hdigest_valid_bytes); + if (rc < 0) { + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + + pdu->hdigest_valid_bytes += rc; + if (pdu->hdigest_valid_bytes < ISCSI_DIGEST_LEN) { + return 0; + } + } + + if (conn->header_digest) { + crc32c = iscsi_pdu_calc_header_digest(pdu); + rc = MATCH_DIGEST_WORD(pdu->header_digest, crc32c); + if (rc == 0) { + SPDK_ERRLOG("header digest error (%s)\n", conn->initiator_name); + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + } + + rc = iscsi_pdu_hdr_handle(conn, pdu); + if (rc < 0) { + SPDK_ERRLOG("Critical error is detected. Close the connection\n"); + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_AWAIT_PDU_PAYLOAD; + break; + case ISCSI_PDU_RECV_STATE_AWAIT_PDU_PAYLOAD: + data_len = pdu->data_segment_len; + + if (data_len != 0 && pdu->data_buf == NULL) { + if (data_len <= iscsi_get_max_immediate_data_size()) { + pool = g_iscsi.pdu_immediate_data_pool; + pdu->data_buf_len = SPDK_BDEV_BUF_SIZE_WITH_MD(iscsi_get_max_immediate_data_size()); + } else if (data_len <= SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH) { + pool = g_iscsi.pdu_data_out_pool; + pdu->data_buf_len = SPDK_BDEV_BUF_SIZE_WITH_MD(SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH); + } else { + SPDK_ERRLOG("Data(%d) > MaxSegment(%d)\n", + data_len, SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH); + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + pdu->mobj = spdk_mempool_get(pool); + if (pdu->mobj == NULL) { + return 0; + } + pdu->data_buf = pdu->mobj->buf; + pdu->data = pdu->mobj->buf; + pdu->data_from_mempool = true; + } + + /* copy the actual data into local buffer */ + if (pdu->data_valid_bytes < data_len) { + rc = iscsi_conn_read_data_segment(conn, pdu, data_len); + if (rc < 0) { + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + + pdu->data_valid_bytes += rc; + if (pdu->data_valid_bytes < data_len) { + return 0; + } + } + + /* copy out the data digest */ + if (conn->data_digest && data_len != 0 && + pdu->ddigest_valid_bytes < ISCSI_DIGEST_LEN) { + rc = iscsi_conn_read_data(conn, + ISCSI_DIGEST_LEN - pdu->ddigest_valid_bytes, + pdu->data_digest + pdu->ddigest_valid_bytes); + if (rc < 0) { + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + + pdu->ddigest_valid_bytes += rc; + if (pdu->ddigest_valid_bytes < ISCSI_DIGEST_LEN) { + return 0; + } + } + + /* All data for this PDU has now been read from the socket. */ + spdk_trace_record(TRACE_ISCSI_READ_PDU, conn->id, pdu->data_valid_bytes, + (uintptr_t)pdu, pdu->bhs.opcode); + + /* check data digest */ + if (conn->data_digest && data_len != 0) { + crc32c = iscsi_pdu_calc_data_digest(pdu); + rc = MATCH_DIGEST_WORD(pdu->data_digest, crc32c); + if (rc == 0) { + SPDK_ERRLOG("data digest error (%s)\n", conn->initiator_name); + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + } + + if (conn->is_logged_out) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "pdu received after logout\n"); + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + break; + } + + if (!pdu->is_rejected) { + rc = iscsi_pdu_payload_handle(conn, pdu); + } else { + rc = 0; + } + if (rc == 0) { + spdk_trace_record(TRACE_ISCSI_TASK_EXECUTED, 0, 0, (uintptr_t)pdu, 0); + iscsi_put_pdu(pdu); + conn->pdu_in_progress = NULL; + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_AWAIT_PDU_READY; + return 1; + } else { + conn->pdu_recv_state = ISCSI_PDU_RECV_STATE_ERROR; + } + break; + case ISCSI_PDU_RECV_STATE_ERROR: + return SPDK_ISCSI_CONNECTION_FATAL; + default: + assert(false); + SPDK_ERRLOG("code should not come here\n"); + break; + } + } while (prev_state != conn->pdu_recv_state); + + return 0; +} + +#define GET_PDU_LOOP_COUNT 16 + +int +iscsi_handle_incoming_pdus(struct spdk_iscsi_conn *conn) +{ + int i, rc; + + /* Read new PDUs from network */ + for (i = 0; i < GET_PDU_LOOP_COUNT; i++) { + rc = iscsi_read_pdu(conn); + if (rc == 0) { + break; + } else if (rc < 0) { + return rc; + } + + if (conn->is_stopped) { + break; + } + } + + return i; +} diff --git a/src/spdk/lib/iscsi/iscsi.h b/src/spdk/lib/iscsi/iscsi.h new file mode 100644 index 000000000..b1747e4ab --- /dev/null +++ b/src/spdk/lib/iscsi/iscsi.h @@ -0,0 +1,465 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SPDK_ISCSI_H +#define SPDK_ISCSI_H + +#include "spdk/stdinc.h" + +#include "spdk/bdev.h" +#include "spdk/iscsi_spec.h" +#include "spdk/thread.h" +#include "spdk/sock.h" + +#include "spdk/scsi.h" +#include "iscsi/param.h" + +#include "spdk/assert.h" +#include "spdk/dif.h" +#include "spdk/util.h" + +#define SPDK_ISCSI_DEFAULT_NODEBASE "iqn.2016-06.io.spdk" + +#define DEFAULT_MAXR2T 4 +#define MAX_INITIATOR_PORT_NAME 256 +#define MAX_INITIATOR_NAME 223 +#define MAX_TARGET_NAME 223 + +#define MAX_PORTAL 1024 +#define MAX_INITIATOR 256 +#define MAX_NETMASK 256 +#define MAX_ISCSI_CONNECTIONS 1024 +#define MAX_PORTAL_ADDR 256 +#define MAX_PORTAL_PORT 32 + +#define DEFAULT_PORT 3260 +#define DEFAULT_MAX_SESSIONS 128 +#define DEFAULT_MAX_CONNECTIONS_PER_SESSION 2 +#define DEFAULT_MAXOUTSTANDINGR2T 1 +#define DEFAULT_DEFAULTTIME2WAIT 2 +#define DEFAULT_DEFAULTTIME2RETAIN 20 +#define DEFAULT_INITIALR2T true +#define DEFAULT_IMMEDIATEDATA true +#define DEFAULT_DATAPDUINORDER true +#define DEFAULT_DATASEQUENCEINORDER true +#define DEFAULT_ERRORRECOVERYLEVEL 0 +#define DEFAULT_TIMEOUT 60 +#define MAX_NOPININTERVAL 60 +#define DEFAULT_NOPININTERVAL 30 + +/* + * SPDK iSCSI target currently only supports 64KB as the maximum data segment length + * it can receive from initiators. Other values may work, but no guarantees. + */ +#define SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH 65536 + +/* + * Defines maximum number of data out buffers each connection can have in + * use at any given time. + */ +#define MAX_DATA_OUT_PER_CONNECTION 16 + +/* + * Defines maximum number of data in buffers each connection can have in + * use at any given time. So this limit does not affect I/O smaller than + * SPDK_BDEV_SMALL_BUF_MAX_SIZE. + */ +#define MAX_LARGE_DATAIN_PER_CONNECTION 64 + +#define SPDK_ISCSI_MAX_BURST_LENGTH \ + (SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH * MAX_DATA_OUT_PER_CONNECTION) + +/* + * Defines default maximum amount in bytes of unsolicited data the iSCSI + * initiator may send to the SPDK iSCSI target during the execution of + * a single SCSI command. And it is smaller than the MaxBurstLength. + */ +#define SPDK_ISCSI_FIRST_BURST_LENGTH 8192 + +/* + * Defines minimum amount in bytes of unsolicited data the iSCSI initiator + * may send to the SPDK iSCSI target during the execution of a single + * SCSI command. + */ +#define SPDK_ISCSI_MIN_FIRST_BURST_LENGTH 512 + +#define SPDK_ISCSI_MAX_FIRST_BURST_LENGTH 16777215 + +/* + * Defines default maximum queue depth per connection and this can be + * changed by configuration file. + */ +#define DEFAULT_MAX_QUEUE_DEPTH 64 + +/** Defines how long we should wait for a logout request when the target + * requests logout to the initiator asynchronously. + */ +#define ISCSI_LOGOUT_REQUEST_TIMEOUT 30 /* in seconds */ + +/** Defines how long we should wait for a TCP close after responding to a + * logout request, before terminating the connection ourselves. + */ +#define ISCSI_LOGOUT_TIMEOUT 5 /* in seconds */ + +/* For spdk_iscsi_login_in related function use, we need to avoid the conflict + * with other errors + * */ +#define SPDK_ISCSI_LOGIN_ERROR_RESPONSE -1000 +#define SPDK_ISCSI_LOGIN_ERROR_PARAMETER -1001 +#define SPDK_ISCSI_PARAMETER_EXCHANGE_NOT_ONCE -1002 + +#define ISCSI_AHS_LEN 60 + +struct spdk_mobj { + struct spdk_mempool *mp; + void *buf; +}; + +/* + * Maximum number of SGL elements, i.e., + * BHS, AHS, Header Digest, Data Segment and Data Digest. + */ +#define SPDK_ISCSI_MAX_SGL_DESCRIPTORS (5) + +typedef void (*iscsi_conn_xfer_complete_cb)(void *cb_arg); + +struct spdk_iscsi_pdu { + struct iscsi_bhs bhs; + struct spdk_mobj *mobj; + bool is_rejected; + uint8_t *data_buf; + uint8_t *data; + uint8_t header_digest[ISCSI_DIGEST_LEN]; + uint8_t data_digest[ISCSI_DIGEST_LEN]; + size_t data_segment_len; + int bhs_valid_bytes; + int ahs_valid_bytes; + uint32_t data_valid_bytes; + int hdigest_valid_bytes; + int ddigest_valid_bytes; + int ref; + bool data_from_mempool; /* indicate whether the data buffer is allocated from mempool */ + struct spdk_iscsi_task *task; /* data tied to a task buffer */ + uint32_t cmd_sn; + uint32_t writev_offset; + uint32_t data_buf_len; + bool dif_insert_or_strip; + struct spdk_dif_ctx dif_ctx; + struct spdk_iscsi_conn *conn; + + iscsi_conn_xfer_complete_cb cb_fn; + void *cb_arg; + + /* The sock request ends with a 0 length iovec. Place the actual iovec immediately + * after it. There is a static assert below to check if the compiler inserted + * any unwanted padding */ + int32_t mapped_length; + struct spdk_sock_request sock_req; + struct iovec iov[SPDK_ISCSI_MAX_SGL_DESCRIPTORS]; + TAILQ_ENTRY(spdk_iscsi_pdu) tailq; + + + /* + * 60 bytes of AHS should suffice for now. + * This should always be at the end of PDU data structure. + * we need to not zero this out when doing memory clear. + */ + uint8_t ahs[ISCSI_AHS_LEN]; + + struct { + uint16_t length; /* iSCSI SenseLength (big-endian) */ + uint8_t data[32]; + } sense; +}; +SPDK_STATIC_ASSERT(offsetof(struct spdk_iscsi_pdu, + sock_req) + sizeof(struct spdk_sock_request) == offsetof(struct spdk_iscsi_pdu, iov), + "Compiler inserted padding between iov and sock_req"); + +enum iscsi_connection_state { + ISCSI_CONN_STATE_INVALID = 0, + ISCSI_CONN_STATE_RUNNING = 1, + ISCSI_CONN_STATE_EXITING = 2, + ISCSI_CONN_STATE_EXITED = 3, +}; + +enum iscsi_chap_phase { + ISCSI_CHAP_PHASE_NONE = 0, + ISCSI_CHAP_PHASE_WAIT_A = 1, + ISCSI_CHAP_PHASE_WAIT_NR = 2, + ISCSI_CHAP_PHASE_END = 3, +}; + +enum session_type { + SESSION_TYPE_INVALID = 0, + SESSION_TYPE_NORMAL = 1, + SESSION_TYPE_DISCOVERY = 2, +}; + +#define ISCSI_CHAP_CHALLENGE_LEN 1024 +#define ISCSI_CHAP_MAX_USER_LEN 255 +#define ISCSI_CHAP_MAX_SECRET_LEN 255 + +struct iscsi_chap_auth { + enum iscsi_chap_phase chap_phase; + + char user[ISCSI_CHAP_MAX_USER_LEN + 1]; + char secret[ISCSI_CHAP_MAX_SECRET_LEN + 1]; + char muser[ISCSI_CHAP_MAX_USER_LEN + 1]; + char msecret[ISCSI_CHAP_MAX_SECRET_LEN + 1]; + + uint8_t chap_id[1]; + uint8_t chap_mid[1]; + int chap_challenge_len; + uint8_t chap_challenge[ISCSI_CHAP_CHALLENGE_LEN]; + int chap_mchallenge_len; + uint8_t chap_mchallenge[ISCSI_CHAP_CHALLENGE_LEN]; +}; + +struct spdk_iscsi_auth_secret { + char user[ISCSI_CHAP_MAX_USER_LEN + 1]; + char secret[ISCSI_CHAP_MAX_SECRET_LEN + 1]; + char muser[ISCSI_CHAP_MAX_USER_LEN + 1]; + char msecret[ISCSI_CHAP_MAX_SECRET_LEN + 1]; + TAILQ_ENTRY(spdk_iscsi_auth_secret) tailq; +}; + +struct spdk_iscsi_auth_group { + int32_t tag; + TAILQ_HEAD(, spdk_iscsi_auth_secret) secret_head; + TAILQ_ENTRY(spdk_iscsi_auth_group) tailq; +}; + +struct spdk_iscsi_sess { + uint32_t connections; + struct spdk_iscsi_conn **conns; + + struct spdk_scsi_port *initiator_port; + int tag; + + uint64_t isid; + uint16_t tsih; + struct spdk_iscsi_tgt_node *target; + int queue_depth; + + struct iscsi_param *params; + + enum session_type session_type; + uint32_t MaxConnections; + uint32_t MaxOutstandingR2T; + uint32_t DefaultTime2Wait; + uint32_t DefaultTime2Retain; + uint32_t FirstBurstLength; + uint32_t MaxBurstLength; + bool InitialR2T; + bool ImmediateData; + bool DataPDUInOrder; + bool DataSequenceInOrder; + uint32_t ErrorRecoveryLevel; + + uint32_t ExpCmdSN; + uint32_t MaxCmdSN; + + uint32_t current_text_itt; +}; + +struct spdk_iscsi_poll_group { + struct spdk_poller *poller; + struct spdk_poller *nop_poller; + STAILQ_HEAD(connections, spdk_iscsi_conn) connections; + struct spdk_sock_group *sock_group; + TAILQ_ENTRY(spdk_iscsi_poll_group) link; +}; + +struct spdk_iscsi_opts { + char *authfile; + char *nodebase; + int32_t timeout; + int32_t nopininterval; + bool disable_chap; + bool require_chap; + bool mutual_chap; + int32_t chap_group; + uint32_t MaxSessions; + uint32_t MaxConnectionsPerSession; + uint32_t MaxConnections; + uint32_t MaxQueueDepth; + uint32_t DefaultTime2Wait; + uint32_t DefaultTime2Retain; + uint32_t FirstBurstLength; + bool ImmediateData; + uint32_t ErrorRecoveryLevel; + bool AllowDuplicateIsid; +}; + +struct spdk_iscsi_globals { + char *authfile; + char *nodebase; + pthread_mutex_t mutex; + uint32_t refcnt; + TAILQ_HEAD(, spdk_iscsi_portal) portal_head; + TAILQ_HEAD(, spdk_iscsi_portal_grp) pg_head; + TAILQ_HEAD(, spdk_iscsi_init_grp) ig_head; + TAILQ_HEAD(, spdk_iscsi_tgt_node) target_head; + TAILQ_HEAD(, spdk_iscsi_auth_group) auth_group_head; + TAILQ_HEAD(, spdk_iscsi_poll_group) poll_group_head; + + int32_t timeout; + int32_t nopininterval; + bool disable_chap; + bool require_chap; + bool mutual_chap; + int32_t chap_group; + + uint32_t MaxSessions; + uint32_t MaxConnectionsPerSession; + uint32_t MaxConnections; + uint32_t MaxQueueDepth; + uint32_t DefaultTime2Wait; + uint32_t DefaultTime2Retain; + uint32_t FirstBurstLength; + bool ImmediateData; + uint32_t ErrorRecoveryLevel; + bool AllowDuplicateIsid; + + struct spdk_mempool *pdu_pool; + struct spdk_mempool *pdu_immediate_data_pool; + struct spdk_mempool *pdu_data_out_pool; + struct spdk_mempool *session_pool; + struct spdk_mempool *task_pool; + + struct spdk_iscsi_sess **session; +}; + +#define ISCSI_SECURITY_NEGOTIATION_PHASE 0 +#define ISCSI_OPERATIONAL_NEGOTIATION_PHASE 1 +#define ISCSI_NSG_RESERVED_CODE 2 +#define ISCSI_FULL_FEATURE_PHASE 3 + +/* logout reason */ +#define ISCSI_LOGOUT_REASON_CLOSE_SESSION 0 +#define ISCSI_LOGOUT_REASON_CLOSE_CONNECTION 1 +#define ISCSI_LOGOUT_REASON_REMOVE_CONN_FOR_RECOVERY 2 + +enum spdk_error_codes { + SPDK_ISCSI_CONNECTION_FATAL = -1, + SPDK_PDU_FATAL = -2, +}; + +#define DGET24(B) \ + ((( (uint32_t) *((uint8_t *)(B)+0)) << 16) \ + | (((uint32_t) *((uint8_t *)(B)+1)) << 8) \ + | (((uint32_t) *((uint8_t *)(B)+2)) << 0)) + +#define DSET24(B,D) \ + (((*((uint8_t *)(B)+0)) = (uint8_t)((uint32_t)(D) >> 16)), \ + ((*((uint8_t *)(B)+1)) = (uint8_t)((uint32_t)(D) >> 8)), \ + ((*((uint8_t *)(B)+2)) = (uint8_t)((uint32_t)(D) >> 0))) + +#define xstrdup(s) (s ? strdup(s) : (char *)NULL) + +extern struct spdk_iscsi_globals g_iscsi; +extern struct spdk_iscsi_opts *g_spdk_iscsi_opts; + +struct spdk_iscsi_task; +struct spdk_json_write_ctx; + +typedef void (*spdk_iscsi_init_cb)(void *cb_arg, int rc); + +void spdk_iscsi_init(spdk_iscsi_init_cb cb_fn, void *cb_arg); +typedef void (*spdk_iscsi_fini_cb)(void *arg); +void spdk_iscsi_fini(spdk_iscsi_fini_cb cb_fn, void *cb_arg); +void shutdown_iscsi_conns_done(void); +void spdk_iscsi_config_text(FILE *fp); +void spdk_iscsi_config_json(struct spdk_json_write_ctx *w); + +struct spdk_iscsi_opts *iscsi_opts_alloc(void); +void iscsi_opts_free(struct spdk_iscsi_opts *opts); +struct spdk_iscsi_opts *iscsi_opts_copy(struct spdk_iscsi_opts *src); +void iscsi_opts_info_json(struct spdk_json_write_ctx *w); +int iscsi_set_discovery_auth(bool disable_chap, bool require_chap, + bool mutual_chap, int32_t chap_group); +int iscsi_chap_get_authinfo(struct iscsi_chap_auth *auth, const char *authuser, + int ag_tag); +int iscsi_add_auth_group(int32_t tag, struct spdk_iscsi_auth_group **_group); +struct spdk_iscsi_auth_group *iscsi_find_auth_group_by_tag(int32_t tag); +void iscsi_delete_auth_group(struct spdk_iscsi_auth_group *group); +int iscsi_auth_group_add_secret(struct spdk_iscsi_auth_group *group, + const char *user, const char *secret, + const char *muser, const char *msecret); +int iscsi_auth_group_delete_secret(struct spdk_iscsi_auth_group *group, + const char *user); +void iscsi_auth_groups_info_json(struct spdk_json_write_ctx *w); + +void iscsi_task_response(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task); +int iscsi_build_iovs(struct spdk_iscsi_conn *conn, struct iovec *iovs, int iovcnt, + struct spdk_iscsi_pdu *pdu, uint32_t *mapped_length); +int iscsi_handle_incoming_pdus(struct spdk_iscsi_conn *conn); +void iscsi_task_mgmt_response(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task); + +void iscsi_free_sess(struct spdk_iscsi_sess *sess); +void iscsi_clear_all_transfer_task(struct spdk_iscsi_conn *conn, + struct spdk_scsi_lun *lun, + struct spdk_iscsi_pdu *pdu); +bool iscsi_del_transfer_task(struct spdk_iscsi_conn *conn, uint32_t CmdSN); + +uint32_t iscsi_pdu_calc_header_digest(struct spdk_iscsi_pdu *pdu); +uint32_t iscsi_pdu_calc_data_digest(struct spdk_iscsi_pdu *pdu); + +/* Memory management */ +void iscsi_put_pdu(struct spdk_iscsi_pdu *pdu); +struct spdk_iscsi_pdu *iscsi_get_pdu(struct spdk_iscsi_conn *conn); +void iscsi_op_abort_task_set(struct spdk_iscsi_task *task, + uint8_t function); +void iscsi_queue_task(struct spdk_iscsi_conn *conn, struct spdk_iscsi_task *task); + +static inline uint32_t +iscsi_get_max_immediate_data_size(void) +{ + /* + * Specify enough extra space in addition to FirstBurstLength to + * account for a header digest, data digest and additional header + * segments (AHS). These are not normally used but they do not + * take up much space and we need to make sure the worst-case scenario + * can be satisified by the size returned here. + */ + return g_iscsi.FirstBurstLength + + ISCSI_DIGEST_LEN + /* data digest */ + ISCSI_DIGEST_LEN + /* header digest */ + 8 + /* bidirectional AHS */ + 52; /* extended CDB AHS (for a 64-byte CDB) */ +} + +#endif /* SPDK_ISCSI_H */ diff --git a/src/spdk/lib/iscsi/iscsi_rpc.c b/src/spdk/lib/iscsi/iscsi_rpc.c new file mode 100644 index 000000000..8ab43d31d --- /dev/null +++ b/src/spdk/lib/iscsi/iscsi_rpc.c @@ -0,0 +1,1639 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "iscsi/iscsi.h" +#include "iscsi/conn.h" +#include "iscsi/tgt_node.h" +#include "iscsi/portal_grp.h" +#include "iscsi/init_grp.h" + +#include "spdk/rpc.h" +#include "spdk/util.h" +#include "spdk/string.h" +#include "spdk_internal/log.h" + +static void +rpc_iscsi_get_initiator_groups(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct spdk_json_write_ctx *w; + + if (params != NULL) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "iscsi_get_initiator_groups requires no parameters"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_array_begin(w); + iscsi_init_grps_info_json(w); + spdk_json_write_array_end(w); + + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("iscsi_get_initiator_groups", rpc_iscsi_get_initiator_groups, + SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_get_initiator_groups, get_initiator_groups) + +struct rpc_initiator_list { + size_t num_initiators; + char *initiators[MAX_INITIATOR]; +}; + +static int +decode_rpc_initiator_list(const struct spdk_json_val *val, void *out) +{ + struct rpc_initiator_list *list = out; + + return spdk_json_decode_array(val, spdk_json_decode_string, list->initiators, MAX_INITIATOR, + &list->num_initiators, sizeof(char *)); +} + +static void +free_rpc_initiator_list(struct rpc_initiator_list *list) +{ + size_t i; + + for (i = 0; i < list->num_initiators; i++) { + free(list->initiators[i]); + } +} + +struct rpc_netmask_list { + size_t num_netmasks; + char *netmasks[MAX_NETMASK]; +}; + +static int +decode_rpc_netmask_list(const struct spdk_json_val *val, void *out) +{ + struct rpc_netmask_list *list = out; + + return spdk_json_decode_array(val, spdk_json_decode_string, list->netmasks, MAX_NETMASK, + &list->num_netmasks, sizeof(char *)); +} + +static void +free_rpc_netmask_list(struct rpc_netmask_list *list) +{ + size_t i; + + for (i = 0; i < list->num_netmasks; i++) { + free(list->netmasks[i]); + } +} + +struct rpc_initiator_group { + int32_t tag; + struct rpc_initiator_list initiator_list; + struct rpc_netmask_list netmask_list; +}; + +static void +free_rpc_initiator_group(struct rpc_initiator_group *ig) +{ + free_rpc_initiator_list(&ig->initiator_list); + free_rpc_netmask_list(&ig->netmask_list); +} + +static const struct spdk_json_object_decoder rpc_initiator_group_decoders[] = { + {"tag", offsetof(struct rpc_initiator_group, tag), spdk_json_decode_int32}, + {"initiators", offsetof(struct rpc_initiator_group, initiator_list), decode_rpc_initiator_list}, + {"netmasks", offsetof(struct rpc_initiator_group, netmask_list), decode_rpc_netmask_list}, +}; + +static void +rpc_iscsi_create_initiator_group(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_initiator_group req = {}; + struct spdk_json_write_ctx *w; + + if (spdk_json_decode_object(params, rpc_initiator_group_decoders, + SPDK_COUNTOF(rpc_initiator_group_decoders), &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + if (req.initiator_list.num_initiators == 0 || + req.netmask_list.num_netmasks == 0) { + goto invalid; + } + + if (iscsi_init_grp_create_from_initiator_list(req.tag, + req.initiator_list.num_initiators, + req.initiator_list.initiators, + req.netmask_list.num_netmasks, + req.netmask_list.netmasks)) { + SPDK_ERRLOG("create_from_initiator_list failed\n"); + goto invalid; + } + + free_rpc_initiator_group(&req); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + return; + +invalid: + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + free_rpc_initiator_group(&req); +} +SPDK_RPC_REGISTER("iscsi_create_initiator_group", rpc_iscsi_create_initiator_group, + SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_create_initiator_group, add_initiator_group) + +static const struct spdk_json_object_decoder rpc_add_or_delete_initiators_decoders[] = { + {"tag", offsetof(struct rpc_initiator_group, tag), spdk_json_decode_int32}, + {"initiators", offsetof(struct rpc_initiator_group, initiator_list), decode_rpc_initiator_list, true}, + {"netmasks", offsetof(struct rpc_initiator_group, netmask_list), decode_rpc_netmask_list, true}, +}; + +static void +rpc_iscsi_initiator_group_add_initiators(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_initiator_group req = {}; + struct spdk_json_write_ctx *w; + + if (spdk_json_decode_object(params, rpc_add_or_delete_initiators_decoders, + SPDK_COUNTOF(rpc_add_or_delete_initiators_decoders), &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + if (iscsi_init_grp_add_initiators_from_initiator_list(req.tag, + req.initiator_list.num_initiators, + req.initiator_list.initiators, + req.netmask_list.num_netmasks, + req.netmask_list.netmasks)) { + SPDK_ERRLOG("add_initiators_from_initiator_list failed\n"); + goto invalid; + } + + free_rpc_initiator_group(&req); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + return; + +invalid: + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + free_rpc_initiator_group(&req); +} +SPDK_RPC_REGISTER("iscsi_initiator_group_add_initiators", + rpc_iscsi_initiator_group_add_initiators, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_initiator_group_add_initiators, + add_initiators_to_initiator_group) + +static void +rpc_iscsi_initiator_group_remove_initiators(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_initiator_group req = {}; + struct spdk_json_write_ctx *w; + + if (spdk_json_decode_object(params, rpc_add_or_delete_initiators_decoders, + SPDK_COUNTOF(rpc_add_or_delete_initiators_decoders), &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + if (iscsi_init_grp_delete_initiators_from_initiator_list(req.tag, + req.initiator_list.num_initiators, + req.initiator_list.initiators, + req.netmask_list.num_netmasks, + req.netmask_list.netmasks)) { + SPDK_ERRLOG("delete_initiators_from_initiator_list failed\n"); + goto invalid; + } + + free_rpc_initiator_group(&req); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + return; + +invalid: + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + free_rpc_initiator_group(&req); +} +SPDK_RPC_REGISTER("iscsi_initiator_group_remove_initiators", + rpc_iscsi_initiator_group_remove_initiators, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_initiator_group_remove_initiators, + delete_initiators_from_initiator_group) + +struct rpc_iscsi_delete_initiator_group { + int32_t tag; +}; + +static const struct spdk_json_object_decoder rpc_iscsi_delete_initiator_group_decoders[] = { + {"tag", offsetof(struct rpc_iscsi_delete_initiator_group, tag), spdk_json_decode_int32}, +}; + +static void +rpc_iscsi_delete_initiator_group(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_iscsi_delete_initiator_group req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_init_grp *ig; + + if (spdk_json_decode_object(params, rpc_iscsi_delete_initiator_group_decoders, + SPDK_COUNTOF(rpc_iscsi_delete_initiator_group_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + ig = iscsi_init_grp_unregister(req.tag); + if (!ig) { + goto invalid; + } + iscsi_tgt_node_delete_map(NULL, ig); + iscsi_init_grp_destroy(ig); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + return; + +invalid: + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); +} +SPDK_RPC_REGISTER("iscsi_delete_initiator_group", rpc_iscsi_delete_initiator_group, + SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_delete_initiator_group, delete_initiator_group) + +static void +rpc_iscsi_get_target_nodes(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct spdk_json_write_ctx *w; + + if (params != NULL) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "iscsi_get_target_nodes requires no parameters"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_array_begin(w); + iscsi_tgt_nodes_info_json(w); + spdk_json_write_array_end(w); + + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("iscsi_get_target_nodes", rpc_iscsi_get_target_nodes, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_get_target_nodes, get_target_nodes) + +struct rpc_pg_ig_map { + int32_t pg_tag; + int32_t ig_tag; +}; + +static const struct spdk_json_object_decoder rpc_pg_ig_map_decoders[] = { + {"pg_tag", offsetof(struct rpc_pg_ig_map, pg_tag), spdk_json_decode_int32}, + {"ig_tag", offsetof(struct rpc_pg_ig_map, ig_tag), spdk_json_decode_int32}, +}; + +static int +decode_rpc_pg_ig_map(const struct spdk_json_val *val, void *out) +{ + struct rpc_pg_ig_map *pg_ig_map = out; + + return spdk_json_decode_object(val, rpc_pg_ig_map_decoders, + SPDK_COUNTOF(rpc_pg_ig_map_decoders), + pg_ig_map); +} + +struct rpc_pg_ig_maps { + size_t num_maps; + struct rpc_pg_ig_map maps[MAX_TARGET_MAP]; +}; + +static int +decode_rpc_pg_ig_maps(const struct spdk_json_val *val, void *out) +{ + struct rpc_pg_ig_maps *pg_ig_maps = out; + + return spdk_json_decode_array(val, decode_rpc_pg_ig_map, pg_ig_maps->maps, + MAX_TARGET_MAP, &pg_ig_maps->num_maps, + sizeof(struct rpc_pg_ig_map)); +} + +#define RPC_ISCSI_CREATE_TARGET_NODE_MAX_LUN 64 + +struct rpc_lun { + char *bdev_name; + int32_t lun_id; +}; + +static const struct spdk_json_object_decoder rpc_lun_decoders[] = { + {"bdev_name", offsetof(struct rpc_lun, bdev_name), spdk_json_decode_string}, + {"lun_id", offsetof(struct rpc_lun, lun_id), spdk_json_decode_int32}, +}; + +static int +decode_rpc_lun(const struct spdk_json_val *val, void *out) +{ + struct rpc_lun *lun = out; + + return spdk_json_decode_object(val, rpc_lun_decoders, + SPDK_COUNTOF(rpc_lun_decoders), lun); +} + +struct rpc_luns { + size_t num_luns; + struct rpc_lun luns[RPC_ISCSI_CREATE_TARGET_NODE_MAX_LUN]; +}; + +static int +decode_rpc_luns(const struct spdk_json_val *val, void *out) +{ + struct rpc_luns *luns = out; + + return spdk_json_decode_array(val, decode_rpc_lun, luns->luns, + RPC_ISCSI_CREATE_TARGET_NODE_MAX_LUN, + &luns->num_luns, sizeof(struct rpc_lun)); +} + +static void +free_rpc_luns(struct rpc_luns *p) +{ + size_t i; + + for (i = 0; i < p->num_luns; i++) { + free(p->luns[i].bdev_name); + } +} + +struct rpc_target_node { + char *name; + char *alias_name; + + struct rpc_pg_ig_maps pg_ig_maps; + struct rpc_luns luns; + + int32_t queue_depth; + bool disable_chap; + bool require_chap; + bool mutual_chap; + int32_t chap_group; + + bool header_digest; + bool data_digest; +}; + +static void +free_rpc_target_node(struct rpc_target_node *req) +{ + free(req->name); + free(req->alias_name); + free_rpc_luns(&req->luns); +} + +static const struct spdk_json_object_decoder rpc_target_node_decoders[] = { + {"name", offsetof(struct rpc_target_node, name), spdk_json_decode_string}, + {"alias_name", offsetof(struct rpc_target_node, alias_name), spdk_json_decode_string}, + {"pg_ig_maps", offsetof(struct rpc_target_node, pg_ig_maps), decode_rpc_pg_ig_maps}, + {"luns", offsetof(struct rpc_target_node, luns), decode_rpc_luns}, + {"queue_depth", offsetof(struct rpc_target_node, queue_depth), spdk_json_decode_int32}, + {"disable_chap", offsetof(struct rpc_target_node, disable_chap), spdk_json_decode_bool, true}, + {"require_chap", offsetof(struct rpc_target_node, require_chap), spdk_json_decode_bool, true}, + {"mutual_chap", offsetof(struct rpc_target_node, mutual_chap), spdk_json_decode_bool, true}, + {"chap_group", offsetof(struct rpc_target_node, chap_group), spdk_json_decode_int32, true}, + {"header_digest", offsetof(struct rpc_target_node, header_digest), spdk_json_decode_bool, true}, + {"data_digest", offsetof(struct rpc_target_node, data_digest), spdk_json_decode_bool, true}, +}; + +static void +rpc_iscsi_create_target_node(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_target_node req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_tgt_node *target; + int32_t pg_tags[MAX_TARGET_MAP] = {0}, ig_tags[MAX_TARGET_MAP] = {0}; + char *bdev_names[RPC_ISCSI_CREATE_TARGET_NODE_MAX_LUN] = {0}; + int32_t lun_ids[RPC_ISCSI_CREATE_TARGET_NODE_MAX_LUN] = {0}; + size_t i; + + if (spdk_json_decode_object(params, rpc_target_node_decoders, + SPDK_COUNTOF(rpc_target_node_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + for (i = 0; i < req.pg_ig_maps.num_maps; i++) { + pg_tags[i] = req.pg_ig_maps.maps[i].pg_tag; + ig_tags[i] = req.pg_ig_maps.maps[i].ig_tag; + } + + for (i = 0; i < req.luns.num_luns; i++) { + bdev_names[i] = req.luns.luns[i].bdev_name; + lun_ids[i] = req.luns.luns[i].lun_id; + } + + /* + * Use default parameters in a few places: + * index = -1 : automatically pick an index for the new target node + * alias = NULL + */ + target = iscsi_tgt_node_construct(-1, req.name, req.alias_name, + pg_tags, + ig_tags, + req.pg_ig_maps.num_maps, + (const char **)bdev_names, + lun_ids, + req.luns.num_luns, + req.queue_depth, + req.disable_chap, + req.require_chap, + req.mutual_chap, + req.chap_group, + req.header_digest, + req.data_digest); + + if (target == NULL) { + goto invalid; + } + + free_rpc_target_node(&req); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + return; + +invalid: + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + free_rpc_target_node(&req); +} +SPDK_RPC_REGISTER("iscsi_create_target_node", rpc_iscsi_create_target_node, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_create_target_node, construct_target_node) + +struct rpc_tgt_node_pg_ig_maps { + char *name; + struct rpc_pg_ig_maps pg_ig_maps; +}; + +static const struct spdk_json_object_decoder rpc_tgt_node_pg_ig_maps_decoders[] = { + {"name", offsetof(struct rpc_tgt_node_pg_ig_maps, name), spdk_json_decode_string}, + {"pg_ig_maps", offsetof(struct rpc_tgt_node_pg_ig_maps, pg_ig_maps), decode_rpc_pg_ig_maps}, +}; + +static void +rpc_iscsi_target_node_add_pg_ig_maps(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_tgt_node_pg_ig_maps req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_tgt_node *target; + int32_t pg_tags[MAX_TARGET_MAP] = {0}, ig_tags[MAX_TARGET_MAP] = {0}; + size_t i; + int rc; + + if (spdk_json_decode_object(params, rpc_tgt_node_pg_ig_maps_decoders, + SPDK_COUNTOF(rpc_tgt_node_pg_ig_maps_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + target = iscsi_find_tgt_node(req.name); + if (target == NULL) { + SPDK_ERRLOG("target is not found\n"); + goto invalid; + } + + for (i = 0; i < req.pg_ig_maps.num_maps; i++) { + pg_tags[i] = req.pg_ig_maps.maps[i].pg_tag; + ig_tags[i] = req.pg_ig_maps.maps[i].ig_tag; + } + + rc = iscsi_target_node_add_pg_ig_maps(target, pg_tags, ig_tags, + req.pg_ig_maps.num_maps); + if (rc < 0) { + SPDK_ERRLOG("add pg-ig maps failed\n"); + goto invalid; + } + + free(req.name); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + return; + +invalid: + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid parameters"); + free(req.name); +} +SPDK_RPC_REGISTER("iscsi_target_node_add_pg_ig_maps", + rpc_iscsi_target_node_add_pg_ig_maps, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_target_node_add_pg_ig_maps, add_pg_ig_maps) + +static void +rpc_iscsi_target_node_remove_pg_ig_maps(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_tgt_node_pg_ig_maps req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_tgt_node *target; + int32_t pg_tags[MAX_TARGET_MAP] = {0}, ig_tags[MAX_TARGET_MAP] = {0}; + size_t i; + int rc; + + if (spdk_json_decode_object(params, rpc_tgt_node_pg_ig_maps_decoders, + SPDK_COUNTOF(rpc_tgt_node_pg_ig_maps_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + target = iscsi_find_tgt_node(req.name); + if (target == NULL) { + SPDK_ERRLOG("target is not found\n"); + goto invalid; + } + + for (i = 0; i < req.pg_ig_maps.num_maps; i++) { + pg_tags[i] = req.pg_ig_maps.maps[i].pg_tag; + ig_tags[i] = req.pg_ig_maps.maps[i].ig_tag; + } + + rc = iscsi_target_node_remove_pg_ig_maps(target, pg_tags, ig_tags, + req.pg_ig_maps.num_maps); + if (rc < 0) { + SPDK_ERRLOG("remove pg-ig maps failed\n"); + goto invalid; + } + + free(req.name); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + return; + +invalid: + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid parameters"); + free(req.name); +} +SPDK_RPC_REGISTER("iscsi_target_node_remove_pg_ig_maps", + rpc_iscsi_target_node_remove_pg_ig_maps, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_target_node_remove_pg_ig_maps, + delete_pg_ig_maps) + +struct rpc_iscsi_delete_target_node { + char *name; +}; + +static void +free_rpc_iscsi_delete_target_node(struct rpc_iscsi_delete_target_node *r) +{ + free(r->name); +} + +static const struct spdk_json_object_decoder rpc_iscsi_delete_target_node_decoders[] = { + {"name", offsetof(struct rpc_iscsi_delete_target_node, name), spdk_json_decode_string}, +}; + +struct rpc_iscsi_delete_target_node_ctx { + struct rpc_iscsi_delete_target_node req; + struct spdk_jsonrpc_request *request; +}; + +static void +rpc_iscsi_delete_target_node_done(void *cb_arg, int rc) +{ + struct rpc_iscsi_delete_target_node_ctx *ctx = cb_arg; + struct spdk_json_write_ctx *w; + + free_rpc_iscsi_delete_target_node(&ctx->req); + + w = spdk_jsonrpc_begin_result(ctx->request); + spdk_json_write_bool(w, rc == 0); + spdk_jsonrpc_end_result(ctx->request, w); + + free(ctx); +} + +static void +rpc_iscsi_delete_target_node(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_iscsi_delete_target_node_ctx *ctx; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + spdk_strerror(ENOMEM)); + return; + } + + if (spdk_json_decode_object(params, rpc_iscsi_delete_target_node_decoders, + SPDK_COUNTOF(rpc_iscsi_delete_target_node_decoders), + &ctx->req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + if (ctx->req.name == NULL) { + SPDK_ERRLOG("missing name param\n"); + goto invalid; + } + + ctx->request = request; + + iscsi_shutdown_tgt_node_by_name(ctx->req.name, + rpc_iscsi_delete_target_node_done, ctx); + return; + +invalid: + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + free_rpc_iscsi_delete_target_node(&ctx->req); + free(ctx); +} +SPDK_RPC_REGISTER("iscsi_delete_target_node", rpc_iscsi_delete_target_node, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_delete_target_node, delete_target_node) + +static void +rpc_iscsi_get_portal_groups(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct spdk_json_write_ctx *w; + + if (params != NULL) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "iscsi_get_portal_groups requires no parameters"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_array_begin(w); + iscsi_portal_grps_info_json(w); + spdk_json_write_array_end(w); + + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("iscsi_get_portal_groups", rpc_iscsi_get_portal_groups, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_get_portal_groups, get_portal_groups) + +struct rpc_portal { + char *host; + char *port; +}; + +struct rpc_portal_list { + size_t num_portals; + struct rpc_portal portals[MAX_PORTAL]; +}; + +struct rpc_portal_group { + int32_t tag; + struct rpc_portal_list portal_list; +}; + +static void +free_rpc_portal(struct rpc_portal *portal) +{ + free(portal->host); + free(portal->port); +} + +static void +free_rpc_portal_list(struct rpc_portal_list *pl) +{ + size_t i; + + for (i = 0; i < pl->num_portals; i++) { + free_rpc_portal(&pl->portals[i]); + } + pl->num_portals = 0; +} + +static void +free_rpc_portal_group(struct rpc_portal_group *pg) +{ + free_rpc_portal_list(&pg->portal_list); +} + +static const struct spdk_json_object_decoder rpc_portal_decoders[] = { + {"host", offsetof(struct rpc_portal, host), spdk_json_decode_string}, + {"port", offsetof(struct rpc_portal, port), spdk_json_decode_string}, +}; + +static int +decode_rpc_portal(const struct spdk_json_val *val, void *out) +{ + struct rpc_portal *portal = out; + + return spdk_json_decode_object(val, rpc_portal_decoders, + SPDK_COUNTOF(rpc_portal_decoders), + portal); +} + +static int +decode_rpc_portal_list(const struct spdk_json_val *val, void *out) +{ + struct rpc_portal_list *list = out; + + return spdk_json_decode_array(val, decode_rpc_portal, list->portals, MAX_PORTAL, &list->num_portals, + sizeof(struct rpc_portal)); +} + +static const struct spdk_json_object_decoder rpc_portal_group_decoders[] = { + {"tag", offsetof(struct rpc_portal_group, tag), spdk_json_decode_int32}, + {"portals", offsetof(struct rpc_portal_group, portal_list), decode_rpc_portal_list}, +}; + +static void +rpc_iscsi_create_portal_group(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_portal_group req = {}; + struct spdk_iscsi_portal_grp *pg = NULL; + struct spdk_iscsi_portal *portal; + struct spdk_json_write_ctx *w; + size_t i = 0; + int rc = -1; + + if (spdk_json_decode_object(params, rpc_portal_group_decoders, + SPDK_COUNTOF(rpc_portal_group_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto out; + } + + pg = iscsi_portal_grp_create(req.tag); + if (pg == NULL) { + SPDK_ERRLOG("portal_grp_create failed\n"); + goto out; + } + for (i = 0; i < req.portal_list.num_portals; i++) { + portal = iscsi_portal_create(req.portal_list.portals[i].host, + req.portal_list.portals[i].port); + if (portal == NULL) { + SPDK_ERRLOG("portal_create failed\n"); + goto out; + } + iscsi_portal_grp_add_portal(pg, portal); + } + + rc = iscsi_portal_grp_open(pg); + if (rc != 0) { + SPDK_ERRLOG("portal_grp_open failed\n"); + goto out; + } + + rc = iscsi_portal_grp_register(pg); + if (rc != 0) { + SPDK_ERRLOG("portal_grp_register failed\n"); + } + +out: + if (rc == 0) { + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + } else { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); + + if (pg != NULL) { + iscsi_portal_grp_release(pg); + } + } + free_rpc_portal_group(&req); +} +SPDK_RPC_REGISTER("iscsi_create_portal_group", rpc_iscsi_create_portal_group, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_create_portal_group, add_portal_group) + +struct rpc_iscsi_delete_portal_group { + int32_t tag; +}; + +static const struct spdk_json_object_decoder rpc_iscsi_delete_portal_group_decoders[] = { + {"tag", offsetof(struct rpc_iscsi_delete_portal_group, tag), spdk_json_decode_int32}, +}; + +static void +rpc_iscsi_delete_portal_group(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_iscsi_delete_portal_group req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_portal_grp *pg; + + if (spdk_json_decode_object(params, rpc_iscsi_delete_portal_group_decoders, + SPDK_COUNTOF(rpc_iscsi_delete_portal_group_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + pg = iscsi_portal_grp_unregister(req.tag); + if (!pg) { + goto invalid; + } + + iscsi_tgt_node_delete_map(pg, NULL); + iscsi_portal_grp_release(pg); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + return; + +invalid: + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, "Invalid parameters"); +} +SPDK_RPC_REGISTER("iscsi_delete_portal_group", rpc_iscsi_delete_portal_group, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_delete_portal_group, delete_portal_group) + +struct rpc_portal_group_auth { + int32_t tag; + bool disable_chap; + bool require_chap; + bool mutual_chap; + int32_t chap_group; +}; + +static const struct spdk_json_object_decoder rpc_portal_group_auth_decoders[] = { + {"tag", offsetof(struct rpc_portal_group_auth, tag), spdk_json_decode_int32}, + {"disable_chap", offsetof(struct rpc_portal_group_auth, disable_chap), spdk_json_decode_bool, true}, + {"require_chap", offsetof(struct rpc_portal_group_auth, require_chap), spdk_json_decode_bool, true}, + {"mutual_chap", offsetof(struct rpc_portal_group_auth, mutual_chap), spdk_json_decode_bool, true}, + {"chap_group", offsetof(struct rpc_portal_group_auth, chap_group), spdk_json_decode_int32, true}, +}; + +static void +rpc_iscsi_portal_group_set_auth(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_portal_group_auth req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_portal_grp *pg; + int rc; + + if (spdk_json_decode_object(params, rpc_portal_group_auth_decoders, + SPDK_COUNTOF(rpc_portal_group_auth_decoders), &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid parameters"); + return; + } + + pthread_mutex_lock(&g_iscsi.mutex); + + pg = iscsi_portal_grp_find_by_tag(req.tag); + if (pg == NULL) { + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Could not find portal group %d", req.tag); + goto exit; + } + + rc = iscsi_portal_grp_set_chap_params(pg, req.disable_chap, req.require_chap, + req.mutual_chap, req.chap_group); + if (rc < 0) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid combination of auth params"); + goto exit; + } + + pthread_mutex_unlock(&g_iscsi.mutex); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + + return; + +exit: + pthread_mutex_unlock(&g_iscsi.mutex); +} +SPDK_RPC_REGISTER("iscsi_portal_group_set_auth", rpc_iscsi_portal_group_set_auth, + SPDK_RPC_RUNTIME) + +struct rpc_iscsi_get_connections_ctx { + struct spdk_jsonrpc_request *request; + struct spdk_json_write_ctx *w; +}; + +static void +_rpc_iscsi_get_connections_done(struct spdk_io_channel_iter *i, int status) +{ + struct rpc_iscsi_get_connections_ctx *ctx = spdk_io_channel_iter_get_ctx(i); + + spdk_json_write_array_end(ctx->w); + spdk_jsonrpc_end_result(ctx->request, ctx->w); + + free(ctx); +} + +static void +_rpc_iscsi_get_connections(struct spdk_io_channel_iter *i) +{ + struct rpc_iscsi_get_connections_ctx *ctx = spdk_io_channel_iter_get_ctx(i); + struct spdk_io_channel *ch = spdk_io_channel_iter_get_channel(i); + struct spdk_iscsi_poll_group *pg = spdk_io_channel_get_ctx(ch); + struct spdk_iscsi_conn *conn; + + STAILQ_FOREACH(conn, &pg->connections, pg_link) { + iscsi_conn_info_json(ctx->w, conn); + } + + spdk_for_each_channel_continue(i, 0); +} + +static void +rpc_iscsi_get_connections(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_iscsi_get_connections_ctx *ctx; + + if (params != NULL) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "iscsi_get_connections requires no parameters"); + return; + } + + ctx = calloc(1, sizeof(struct rpc_iscsi_get_connections_ctx)); + if (ctx == NULL) { + SPDK_ERRLOG("Failed to allocate rpc_get_iscsi_conns_ctx struct\n"); + spdk_jsonrpc_send_error_response(request, -ENOMEM, spdk_strerror(ENOMEM)); + return; + } + + ctx->request = request; + ctx->w = spdk_jsonrpc_begin_result(request); + + spdk_json_write_array_begin(ctx->w); + + spdk_for_each_channel(&g_iscsi, + _rpc_iscsi_get_connections, + ctx, + _rpc_iscsi_get_connections_done); +} +SPDK_RPC_REGISTER("iscsi_get_connections", rpc_iscsi_get_connections, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_get_connections, get_iscsi_connections) + +struct rpc_target_lun { + char *name; + char *bdev_name; + int32_t lun_id; +}; + +static void +free_rpc_target_lun(struct rpc_target_lun *req) +{ + free(req->name); + free(req->bdev_name); +} + +static const struct spdk_json_object_decoder rpc_target_lun_decoders[] = { + {"name", offsetof(struct rpc_target_lun, name), spdk_json_decode_string}, + {"bdev_name", offsetof(struct rpc_target_lun, bdev_name), spdk_json_decode_string}, + {"lun_id", offsetof(struct rpc_target_lun, lun_id), spdk_json_decode_int32, true}, +}; + +static void +rpc_iscsi_target_node_add_lun(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_target_lun req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_tgt_node *target; + int rc; + + req.lun_id = -1; + + if (spdk_json_decode_object(params, rpc_target_lun_decoders, + SPDK_COUNTOF(rpc_target_lun_decoders), &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + target = iscsi_find_tgt_node(req.name); + if (target == NULL) { + SPDK_ERRLOG("target is not found\n"); + goto invalid; + } + + rc = iscsi_tgt_node_add_lun(target, req.bdev_name, req.lun_id); + if (rc < 0) { + SPDK_ERRLOG("add lun failed\n"); + goto invalid; + } + + free_rpc_target_lun(&req); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + return; + +invalid: + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid parameters"); + free_rpc_target_lun(&req); +} +SPDK_RPC_REGISTER("iscsi_target_node_add_lun", rpc_iscsi_target_node_add_lun, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_target_node_add_lun, target_node_add_lun) + +struct rpc_target_auth { + char *name; + bool disable_chap; + bool require_chap; + bool mutual_chap; + int32_t chap_group; +}; + +static void +free_rpc_target_auth(struct rpc_target_auth *req) +{ + free(req->name); +} + +static const struct spdk_json_object_decoder rpc_target_auth_decoders[] = { + {"name", offsetof(struct rpc_target_auth, name), spdk_json_decode_string}, + {"disable_chap", offsetof(struct rpc_target_auth, disable_chap), spdk_json_decode_bool, true}, + {"require_chap", offsetof(struct rpc_target_auth, require_chap), spdk_json_decode_bool, true}, + {"mutual_chap", offsetof(struct rpc_target_auth, mutual_chap), spdk_json_decode_bool, true}, + {"chap_group", offsetof(struct rpc_target_auth, chap_group), spdk_json_decode_int32, true}, +}; + +static void +rpc_iscsi_target_node_set_auth(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_target_auth req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_tgt_node *target; + int rc; + + if (spdk_json_decode_object(params, rpc_target_auth_decoders, + SPDK_COUNTOF(rpc_target_auth_decoders), &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid parameters"); + goto exit; + } + + target = iscsi_find_tgt_node(req.name); + if (target == NULL) { + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Could not find target %s", req.name); + goto exit; + } + + rc = iscsi_tgt_node_set_chap_params(target, req.disable_chap, req.require_chap, + req.mutual_chap, req.chap_group); + if (rc < 0) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid combination of auth params"); + goto exit; + } + + free_rpc_target_auth(&req); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); + return; + +exit: + free_rpc_target_auth(&req); +} +SPDK_RPC_REGISTER("iscsi_target_node_set_auth", rpc_iscsi_target_node_set_auth, + SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_target_node_set_auth, set_iscsi_target_node_auth) + +static void +rpc_iscsi_get_options(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct spdk_json_write_ctx *w; + + if (params != NULL) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "iscsi_get_options requires no parameters"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + iscsi_opts_info_json(w); + + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("iscsi_get_options", rpc_iscsi_get_options, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_get_options, get_iscsi_global_params) + +struct rpc_discovery_auth { + bool disable_chap; + bool require_chap; + bool mutual_chap; + int32_t chap_group; +}; + +static const struct spdk_json_object_decoder rpc_discovery_auth_decoders[] = { + {"disable_chap", offsetof(struct rpc_discovery_auth, disable_chap), spdk_json_decode_bool, true}, + {"require_chap", offsetof(struct rpc_discovery_auth, require_chap), spdk_json_decode_bool, true}, + {"mutual_chap", offsetof(struct rpc_discovery_auth, mutual_chap), spdk_json_decode_bool, true}, + {"chap_group", offsetof(struct rpc_discovery_auth, chap_group), spdk_json_decode_int32, true}, +}; + +static void +rpc_iscsi_set_discovery_auth(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_discovery_auth req = {}; + struct spdk_json_write_ctx *w; + int rc; + + if (spdk_json_decode_object(params, rpc_discovery_auth_decoders, + SPDK_COUNTOF(rpc_discovery_auth_decoders), &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid parameters"); + return; + } + + rc = iscsi_set_discovery_auth(req.disable_chap, req.require_chap, + req.mutual_chap, req.chap_group); + if (rc < 0) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid combination of CHAP params"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("iscsi_set_discovery_auth", rpc_iscsi_set_discovery_auth, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_set_discovery_auth, set_iscsi_discovery_auth) + +#define MAX_AUTH_SECRETS 64 + +struct rpc_auth_secret { + char *user; + char *secret; + char *muser; + char *msecret; +}; + +static void +free_rpc_auth_secret(struct rpc_auth_secret *_secret) +{ + free(_secret->user); + free(_secret->secret); + free(_secret->muser); + free(_secret->msecret); +} + +static const struct spdk_json_object_decoder rpc_auth_secret_decoders[] = { + {"user", offsetof(struct rpc_auth_secret, user), spdk_json_decode_string}, + {"secret", offsetof(struct rpc_auth_secret, secret), spdk_json_decode_string}, + {"muser", offsetof(struct rpc_auth_secret, muser), spdk_json_decode_string, true}, + {"msecret", offsetof(struct rpc_auth_secret, msecret), spdk_json_decode_string, true}, +}; + +static int +decode_rpc_auth_secret(const struct spdk_json_val *val, void *out) +{ + struct rpc_auth_secret *_secret = out; + + return spdk_json_decode_object(val, rpc_auth_secret_decoders, + SPDK_COUNTOF(rpc_auth_secret_decoders), _secret); +} + +struct rpc_auth_secrets { + size_t num_secret; + struct rpc_auth_secret secrets[MAX_AUTH_SECRETS]; +}; + +static void +free_rpc_auth_secrets(struct rpc_auth_secrets *secrets) +{ + size_t i; + + for (i = 0; i < secrets->num_secret; i++) { + free_rpc_auth_secret(&secrets->secrets[i]); + } +} + +static int +decode_rpc_auth_secrets(const struct spdk_json_val *val, void *out) +{ + struct rpc_auth_secrets *secrets = out; + + return spdk_json_decode_array(val, decode_rpc_auth_secret, secrets->secrets, + MAX_AUTH_SECRETS, &secrets->num_secret, + sizeof(struct rpc_auth_secret)); +} + +struct rpc_auth_group { + int32_t tag; + struct rpc_auth_secrets secrets; +}; + +static void +free_rpc_auth_group(struct rpc_auth_group *group) +{ + free_rpc_auth_secrets(&group->secrets); +} + +static const struct spdk_json_object_decoder rpc_auth_group_decoders[] = { + {"tag", offsetof(struct rpc_auth_group, tag), spdk_json_decode_int32}, + {"secrets", offsetof(struct rpc_auth_group, secrets), decode_rpc_auth_secrets, true}, +}; + +static void +rpc_iscsi_create_auth_group(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_auth_group req = {}; + struct rpc_auth_secret *_secret; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_auth_group *group = NULL; + int rc; + size_t i; + + if (spdk_json_decode_object(params, rpc_auth_group_decoders, + SPDK_COUNTOF(rpc_auth_group_decoders), &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid parameters"); + free_rpc_auth_group(&req); + return; + } + + pthread_mutex_lock(&g_iscsi.mutex); + + rc = iscsi_add_auth_group(req.tag, &group); + if (rc != 0) { + pthread_mutex_unlock(&g_iscsi.mutex); + + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Could not add auth group (%d), %s", + req.tag, spdk_strerror(-rc)); + free_rpc_auth_group(&req); + return; + } + + for (i = 0; i < req.secrets.num_secret; i++) { + _secret = &req.secrets.secrets[i]; + rc = iscsi_auth_group_add_secret(group, _secret->user, _secret->secret, + _secret->muser, _secret->msecret); + if (rc != 0) { + iscsi_delete_auth_group(group); + pthread_mutex_unlock(&g_iscsi.mutex); + + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Could not add secret to auth group (%d), %s", + req.tag, spdk_strerror(-rc)); + free_rpc_auth_group(&req); + return; + } + } + + pthread_mutex_unlock(&g_iscsi.mutex); + + free_rpc_auth_group(&req); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("iscsi_create_auth_group", rpc_iscsi_create_auth_group, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_create_auth_group, add_iscsi_auth_group) + +struct rpc_delete_auth_group { + int32_t tag; +}; + +static const struct spdk_json_object_decoder rpc_delete_auth_group_decoders[] = { + {"tag", offsetof(struct rpc_delete_auth_group, tag), spdk_json_decode_int32}, +}; + +static void +rpc_iscsi_delete_auth_group(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_delete_auth_group req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_auth_group *group; + + if (spdk_json_decode_object(params, rpc_delete_auth_group_decoders, + SPDK_COUNTOF(rpc_delete_auth_group_decoders), &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid parameters"); + return; + } + + pthread_mutex_lock(&g_iscsi.mutex); + + group = iscsi_find_auth_group_by_tag(req.tag); + if (group == NULL) { + pthread_mutex_unlock(&g_iscsi.mutex); + + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Could not find auth group (%d)", req.tag); + return; + } + + iscsi_delete_auth_group(group); + + pthread_mutex_unlock(&g_iscsi.mutex); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("iscsi_delete_auth_group", rpc_iscsi_delete_auth_group, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_delete_auth_group, delete_iscsi_auth_group) + +struct rpc_add_auth_secret { + int32_t tag; + char *user; + char *secret; + char *muser; + char *msecret; +}; + +static void +free_rpc_add_auth_secret(struct rpc_add_auth_secret *_secret) +{ + free(_secret->user); + free(_secret->secret); + free(_secret->muser); + free(_secret->msecret); +} + +static const struct spdk_json_object_decoder rpc_add_auth_secret_decoders[] = { + {"tag", offsetof(struct rpc_add_auth_secret, tag), spdk_json_decode_int32}, + {"user", offsetof(struct rpc_add_auth_secret, user), spdk_json_decode_string}, + {"secret", offsetof(struct rpc_add_auth_secret, secret), spdk_json_decode_string}, + {"muser", offsetof(struct rpc_add_auth_secret, muser), spdk_json_decode_string, true}, + {"msecret", offsetof(struct rpc_add_auth_secret, msecret), spdk_json_decode_string, true}, +}; + +static void +rpc_iscsi_auth_group_add_secret(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_add_auth_secret req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_auth_group *group; + int rc; + + if (spdk_json_decode_object(params, rpc_add_auth_secret_decoders, + SPDK_COUNTOF(rpc_add_auth_secret_decoders), &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid parameters"); + free_rpc_add_auth_secret(&req); + return; + } + + pthread_mutex_lock(&g_iscsi.mutex); + + group = iscsi_find_auth_group_by_tag(req.tag); + if (group == NULL) { + pthread_mutex_unlock(&g_iscsi.mutex); + + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Could not find auth group (%d)", req.tag); + free_rpc_add_auth_secret(&req); + return; + } + + rc = iscsi_auth_group_add_secret(group, req.user, req.secret, req.muser, req.msecret); + if (rc != 0) { + pthread_mutex_unlock(&g_iscsi.mutex); + + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Could not add secret to auth group (%d), %s", + req.tag, spdk_strerror(-rc)); + free_rpc_add_auth_secret(&req); + return; + } + + pthread_mutex_unlock(&g_iscsi.mutex); + + free_rpc_add_auth_secret(&req); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("iscsi_auth_group_add_secret", rpc_iscsi_auth_group_add_secret, + SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_auth_group_add_secret, add_secret_to_iscsi_auth_group) + + +struct rpc_remove_auth_secret { + int32_t tag; + char *user; +}; + +static void +free_rpc_remove_auth_secret(struct rpc_remove_auth_secret *_secret) +{ + free(_secret->user); +} + +static const struct spdk_json_object_decoder rpc_remove_auth_secret_decoders[] = { + {"tag", offsetof(struct rpc_remove_auth_secret, tag), spdk_json_decode_int32}, + {"user", offsetof(struct rpc_remove_auth_secret, user), spdk_json_decode_string}, +}; + +static void +rpc_iscsi_auth_group_remove_secret(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_remove_auth_secret req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_auth_group *group; + int rc; + + if (spdk_json_decode_object(params, rpc_remove_auth_secret_decoders, + SPDK_COUNTOF(rpc_remove_auth_secret_decoders), &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid parameters"); + free_rpc_remove_auth_secret(&req); + return; + } + + pthread_mutex_lock(&g_iscsi.mutex); + + group = iscsi_find_auth_group_by_tag(req.tag); + if (group == NULL) { + pthread_mutex_unlock(&g_iscsi.mutex); + + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Could not find auth group (%d)", req.tag); + free_rpc_remove_auth_secret(&req); + return; + } + + rc = iscsi_auth_group_delete_secret(group, req.user); + if (rc != 0) { + pthread_mutex_unlock(&g_iscsi.mutex); + + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Could not delete secret from CHAP group (%d), %s", + req.tag, spdk_strerror(-rc)); + free_rpc_remove_auth_secret(&req); + return; + } + + pthread_mutex_unlock(&g_iscsi.mutex); + + free_rpc_remove_auth_secret(&req); + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("iscsi_auth_group_remove_secret", + rpc_iscsi_auth_group_remove_secret, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_auth_group_remove_secret, + delete_secret_from_iscsi_auth_group) + +static void +rpc_iscsi_get_auth_groups(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct spdk_json_write_ctx *w; + + if (params != NULL) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "iscsi_get_auth_groups requires no parameters"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_array_begin(w); + iscsi_auth_groups_info_json(w); + spdk_json_write_array_end(w); + + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("iscsi_get_auth_groups", rpc_iscsi_get_auth_groups, SPDK_RPC_RUNTIME) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_get_auth_groups, get_iscsi_auth_groups) + +static const struct spdk_json_object_decoder rpc_set_iscsi_opts_decoders[] = { + {"auth_file", offsetof(struct spdk_iscsi_opts, authfile), spdk_json_decode_string, true}, + {"node_base", offsetof(struct spdk_iscsi_opts, nodebase), spdk_json_decode_string, true}, + {"nop_timeout", offsetof(struct spdk_iscsi_opts, timeout), spdk_json_decode_int32, true}, + {"nop_in_interval", offsetof(struct spdk_iscsi_opts, nopininterval), spdk_json_decode_int32, true}, + {"no_discovery_auth", offsetof(struct spdk_iscsi_opts, disable_chap), spdk_json_decode_bool, true}, + {"req_discovery_auth", offsetof(struct spdk_iscsi_opts, require_chap), spdk_json_decode_bool, true}, + {"req_discovery_auth_mutual", offsetof(struct spdk_iscsi_opts, mutual_chap), spdk_json_decode_bool, true}, + {"discovery_auth_group", offsetof(struct spdk_iscsi_opts, chap_group), spdk_json_decode_int32, true}, + {"disable_chap", offsetof(struct spdk_iscsi_opts, disable_chap), spdk_json_decode_bool, true}, + {"require_chap", offsetof(struct spdk_iscsi_opts, require_chap), spdk_json_decode_bool, true}, + {"mutual_chap", offsetof(struct spdk_iscsi_opts, mutual_chap), spdk_json_decode_bool, true}, + {"chap_group", offsetof(struct spdk_iscsi_opts, chap_group), spdk_json_decode_int32, true}, + {"max_sessions", offsetof(struct spdk_iscsi_opts, MaxSessions), spdk_json_decode_uint32, true}, + {"max_queue_depth", offsetof(struct spdk_iscsi_opts, MaxQueueDepth), spdk_json_decode_uint32, true}, + {"max_connections_per_session", offsetof(struct spdk_iscsi_opts, MaxConnectionsPerSession), spdk_json_decode_uint32, true}, + {"default_time2wait", offsetof(struct spdk_iscsi_opts, DefaultTime2Wait), spdk_json_decode_uint32, true}, + {"default_time2retain", offsetof(struct spdk_iscsi_opts, DefaultTime2Retain), spdk_json_decode_uint32, true}, + {"first_burst_length", offsetof(struct spdk_iscsi_opts, FirstBurstLength), spdk_json_decode_uint32, true}, + {"immediate_data", offsetof(struct spdk_iscsi_opts, ImmediateData), spdk_json_decode_bool, true}, + {"error_recovery_level", offsetof(struct spdk_iscsi_opts, ErrorRecoveryLevel), spdk_json_decode_uint32, true}, + {"allow_duplicated_isid", offsetof(struct spdk_iscsi_opts, AllowDuplicateIsid), spdk_json_decode_bool, true}, +}; + +static void +rpc_iscsi_set_options(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct spdk_iscsi_opts *opts; + struct spdk_json_write_ctx *w; + + if (g_spdk_iscsi_opts != NULL) { + SPDK_ERRLOG("this RPC must not be called more than once.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "Must not call more than once"); + return; + } + + opts = iscsi_opts_alloc(); + if (opts == NULL) { + SPDK_ERRLOG("iscsi_opts_alloc() failed.\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "Out of memory"); + return; + } + + if (params != NULL) { + if (spdk_json_decode_object(params, rpc_set_iscsi_opts_decoders, + SPDK_COUNTOF(rpc_set_iscsi_opts_decoders), opts)) { + SPDK_ERRLOG("spdk_json_decode_object() failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Invalid parameters"); + iscsi_opts_free(opts); + return; + } + } + + g_spdk_iscsi_opts = iscsi_opts_copy(opts); + iscsi_opts_free(opts); + + if (g_spdk_iscsi_opts == NULL) { + SPDK_ERRLOG("iscsi_opts_copy() failed\n"); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + "Out of memory"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("iscsi_set_options", rpc_iscsi_set_options, SPDK_RPC_STARTUP) +SPDK_RPC_REGISTER_ALIAS_DEPRECATED(iscsi_set_options, set_iscsi_options) diff --git a/src/spdk/lib/iscsi/iscsi_subsystem.c b/src/spdk/lib/iscsi/iscsi_subsystem.c new file mode 100644 index 000000000..1eb766233 --- /dev/null +++ b/src/spdk/lib/iscsi/iscsi_subsystem.c @@ -0,0 +1,1577 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" +#include "spdk/env.h" +#include "spdk/string.h" +#include "spdk/sock.h" +#include "spdk/likely.h" + +#include "iscsi/iscsi.h" +#include "iscsi/init_grp.h" +#include "iscsi/portal_grp.h" +#include "iscsi/conn.h" +#include "iscsi/task.h" +#include "iscsi/tgt_node.h" + +#include "spdk_internal/event.h" +#include "spdk_internal/log.h" + +struct spdk_iscsi_opts *g_spdk_iscsi_opts = NULL; + +static struct spdk_thread *g_init_thread = NULL; +static spdk_iscsi_init_cb g_init_cb_fn = NULL; +static void *g_init_cb_arg = NULL; + +static spdk_iscsi_fini_cb g_fini_cb_fn; +static void *g_fini_cb_arg; + +#define ISCSI_CONFIG_TMPL \ +"[iSCSI]\n" \ +" # node name (not include optional part)\n" \ +" # Users can optionally change this to fit their environment.\n" \ +" NodeBase \"%s\"\n" \ +"\n" \ +" # files\n" \ +" %s %s\n" \ +"\n" \ +" # socket I/O timeout sec. (polling is infinity)\n" \ +" Timeout %d\n" \ +"\n" \ +" # authentication information for discovery session\n" \ +" DiscoveryAuthMethod %s\n" \ +" DiscoveryAuthGroup %s\n" \ +"\n" \ +" MaxSessions %d\n" \ +" MaxConnectionsPerSession %d\n" \ +" MaxConnections %d\n" \ +" MaxQueueDepth %d\n" \ +"\n" \ +" # iSCSI initial parameters negotiate with initiators\n" \ +" # NOTE: incorrect values might crash\n" \ +" DefaultTime2Wait %d\n" \ +" DefaultTime2Retain %d\n" \ +"\n" \ +" FirstBurstLength %d\n" \ +" ImmediateData %s\n" \ +" ErrorRecoveryLevel %d\n" \ +"\n" + +static void +iscsi_globals_config_text(FILE *fp) +{ + const char *authmethod = "None"; + char authgroup[32] = "None"; + + if (NULL == fp) { + return; + } + + if (g_iscsi.require_chap) { + authmethod = "CHAP"; + } else if (g_iscsi.mutual_chap) { + authmethod = "CHAP Mutual"; + } else if (!g_iscsi.disable_chap) { + authmethod = "Auto"; + } + + if (g_iscsi.chap_group) { + snprintf(authgroup, sizeof(authgroup), "AuthGroup%d", g_iscsi.chap_group); + } + + fprintf(fp, ISCSI_CONFIG_TMPL, + g_iscsi.nodebase, + g_iscsi.authfile ? "AuthFile" : "", + g_iscsi.authfile ? g_iscsi.authfile : "", + g_iscsi.timeout, authmethod, authgroup, + g_iscsi.MaxSessions, g_iscsi.MaxConnectionsPerSession, + g_iscsi.MaxConnections, + g_iscsi.MaxQueueDepth, + g_iscsi.DefaultTime2Wait, g_iscsi.DefaultTime2Retain, + g_iscsi.FirstBurstLength, + (g_iscsi.ImmediateData) ? "Yes" : "No", + g_iscsi.ErrorRecoveryLevel); +} + +#define ISCSI_DATA_BUFFER_ALIGNMENT (0x1000) +#define ISCSI_DATA_BUFFER_MASK (ISCSI_DATA_BUFFER_ALIGNMENT - 1) + +static void +mobj_ctor(struct spdk_mempool *mp, __attribute__((unused)) void *arg, + void *_m, __attribute__((unused)) unsigned i) +{ + struct spdk_mobj *m = _m; + + m->mp = mp; + m->buf = (uint8_t *)m + sizeof(struct spdk_mobj); + m->buf = (void *)((unsigned long)((uint8_t *)m->buf + ISCSI_DATA_BUFFER_ALIGNMENT) & + ~ISCSI_DATA_BUFFER_MASK); +} + +#define NUM_PDU_PER_CONNECTION(iscsi) (2 * (iscsi->MaxQueueDepth + MAX_LARGE_DATAIN_PER_CONNECTION + 8)) +#define PDU_POOL_SIZE(iscsi) (iscsi->MaxConnections * NUM_PDU_PER_CONNECTION(iscsi)) +#define IMMEDIATE_DATA_POOL_SIZE(iscsi) (iscsi->MaxConnections * 128) +#define DATA_OUT_POOL_SIZE(iscsi) (iscsi->MaxConnections * MAX_DATA_OUT_PER_CONNECTION) + +static int +iscsi_initialize_pdu_pool(void) +{ + struct spdk_iscsi_globals *iscsi = &g_iscsi; + int imm_mobj_size = SPDK_BDEV_BUF_SIZE_WITH_MD(iscsi_get_max_immediate_data_size()) + + sizeof(struct spdk_mobj) + ISCSI_DATA_BUFFER_ALIGNMENT; + int dout_mobj_size = SPDK_BDEV_BUF_SIZE_WITH_MD(SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH) + + sizeof(struct spdk_mobj) + ISCSI_DATA_BUFFER_ALIGNMENT; + + /* create PDU pool */ + iscsi->pdu_pool = spdk_mempool_create("PDU_Pool", + PDU_POOL_SIZE(iscsi), + sizeof(struct spdk_iscsi_pdu), + 256, SPDK_ENV_SOCKET_ID_ANY); + if (!iscsi->pdu_pool) { + SPDK_ERRLOG("create PDU pool failed\n"); + return -1; + } + + iscsi->pdu_immediate_data_pool = spdk_mempool_create_ctor("PDU_immediate_data_Pool", + IMMEDIATE_DATA_POOL_SIZE(iscsi), + imm_mobj_size, 256, + SPDK_ENV_SOCKET_ID_ANY, + mobj_ctor, NULL); + if (!iscsi->pdu_immediate_data_pool) { + SPDK_ERRLOG("create PDU immediate data pool failed\n"); + return -1; + } + + iscsi->pdu_data_out_pool = spdk_mempool_create_ctor("PDU_data_out_Pool", + DATA_OUT_POOL_SIZE(iscsi), + dout_mobj_size, 256, + SPDK_ENV_SOCKET_ID_ANY, + mobj_ctor, NULL); + if (!iscsi->pdu_data_out_pool) { + SPDK_ERRLOG("create PDU data out pool failed\n"); + return -1; + } + + return 0; +} + +static void +iscsi_sess_ctor(struct spdk_mempool *pool, void *arg, void *session_buf, + unsigned index) +{ + struct spdk_iscsi_globals *iscsi = arg; + struct spdk_iscsi_sess *sess = session_buf; + + iscsi->session[index] = sess; + + /* tsih 0 is reserved, so start tsih values at 1. */ + sess->tsih = index + 1; +} + +#define DEFAULT_TASK_POOL_SIZE 32768 + +static int +iscsi_initialize_task_pool(void) +{ + struct spdk_iscsi_globals *iscsi = &g_iscsi; + + /* create scsi_task pool */ + iscsi->task_pool = spdk_mempool_create("SCSI_TASK_Pool", + DEFAULT_TASK_POOL_SIZE, + sizeof(struct spdk_iscsi_task), + 128, SPDK_ENV_SOCKET_ID_ANY); + if (!iscsi->task_pool) { + SPDK_ERRLOG("create task pool failed\n"); + return -1; + } + + return 0; +} + +#define SESSION_POOL_SIZE(iscsi) (iscsi->MaxSessions) +static int +iscsi_initialize_session_pool(void) +{ + struct spdk_iscsi_globals *iscsi = &g_iscsi; + + iscsi->session_pool = spdk_mempool_create_ctor("Session_Pool", + SESSION_POOL_SIZE(iscsi), + sizeof(struct spdk_iscsi_sess), 0, + SPDK_ENV_SOCKET_ID_ANY, + iscsi_sess_ctor, iscsi); + if (!iscsi->session_pool) { + SPDK_ERRLOG("create session pool failed\n"); + return -1; + } + + return 0; +} + +static int +iscsi_initialize_all_pools(void) +{ + if (iscsi_initialize_pdu_pool() != 0) { + return -1; + } + + if (iscsi_initialize_session_pool() != 0) { + return -1; + } + + if (iscsi_initialize_task_pool() != 0) { + return -1; + } + + return 0; +} + +static void +iscsi_check_pool(struct spdk_mempool *pool, size_t count) +{ + if (pool && spdk_mempool_count(pool) != count) { + SPDK_ERRLOG("spdk_mempool_count(%s) == %zu, should be %zu\n", + spdk_mempool_get_name(pool), spdk_mempool_count(pool), count); + } +} + +static void +iscsi_check_pools(void) +{ + struct spdk_iscsi_globals *iscsi = &g_iscsi; + + iscsi_check_pool(iscsi->pdu_pool, PDU_POOL_SIZE(iscsi)); + iscsi_check_pool(iscsi->session_pool, SESSION_POOL_SIZE(iscsi)); + iscsi_check_pool(iscsi->pdu_immediate_data_pool, IMMEDIATE_DATA_POOL_SIZE(iscsi)); + iscsi_check_pool(iscsi->pdu_data_out_pool, DATA_OUT_POOL_SIZE(iscsi)); + iscsi_check_pool(iscsi->task_pool, DEFAULT_TASK_POOL_SIZE); +} + +static void +iscsi_free_pools(void) +{ + struct spdk_iscsi_globals *iscsi = &g_iscsi; + + spdk_mempool_free(iscsi->pdu_pool); + spdk_mempool_free(iscsi->session_pool); + spdk_mempool_free(iscsi->pdu_immediate_data_pool); + spdk_mempool_free(iscsi->pdu_data_out_pool); + spdk_mempool_free(iscsi->task_pool); +} + +void iscsi_put_pdu(struct spdk_iscsi_pdu *pdu) +{ + if (!pdu) { + return; + } + + assert(pdu->ref > 0); + pdu->ref--; + + if (pdu->ref == 0) { + if (pdu->mobj) { + spdk_mempool_put(pdu->mobj->mp, (void *)pdu->mobj); + } + + if (pdu->data && !pdu->data_from_mempool) { + free(pdu->data); + } + + spdk_mempool_put(g_iscsi.pdu_pool, (void *)pdu); + } +} + +struct spdk_iscsi_pdu *iscsi_get_pdu(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_pdu *pdu; + + assert(conn != NULL); + pdu = spdk_mempool_get(g_iscsi.pdu_pool); + if (!pdu) { + SPDK_ERRLOG("Unable to get PDU\n"); + abort(); + } + + /* we do not want to zero out the last part of the structure reserved for AHS and sense data */ + memset(pdu, 0, offsetof(struct spdk_iscsi_pdu, ahs)); + pdu->ref = 1; + pdu->conn = conn; + + return pdu; +} + +static void +iscsi_log_globals(void) +{ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "AuthFile %s\n", + g_iscsi.authfile ? g_iscsi.authfile : "(none)"); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "NodeBase %s\n", g_iscsi.nodebase); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "MaxSessions %d\n", g_iscsi.MaxSessions); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "MaxConnectionsPerSession %d\n", + g_iscsi.MaxConnectionsPerSession); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "MaxQueueDepth %d\n", g_iscsi.MaxQueueDepth); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "DefaultTime2Wait %d\n", + g_iscsi.DefaultTime2Wait); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "DefaultTime2Retain %d\n", + g_iscsi.DefaultTime2Retain); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "FirstBurstLength %d\n", + g_iscsi.FirstBurstLength); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "ImmediateData %s\n", + g_iscsi.ImmediateData ? "Yes" : "No"); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "AllowDuplicateIsid %s\n", + g_iscsi.AllowDuplicateIsid ? "Yes" : "No"); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "ErrorRecoveryLevel %d\n", + g_iscsi.ErrorRecoveryLevel); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Timeout %d\n", g_iscsi.timeout); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "NopInInterval %d\n", + g_iscsi.nopininterval); + if (g_iscsi.disable_chap) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "DiscoveryAuthMethod None\n"); + } else if (!g_iscsi.require_chap) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "DiscoveryAuthMethod Auto\n"); + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "DiscoveryAuthMethod %s %s\n", + g_iscsi.require_chap ? "CHAP" : "", + g_iscsi.mutual_chap ? "Mutual" : ""); + } + + if (g_iscsi.chap_group == 0) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "DiscoveryAuthGroup None\n"); + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "DiscoveryAuthGroup AuthGroup%d\n", + g_iscsi.chap_group); + } +} + +static void +iscsi_opts_init(struct spdk_iscsi_opts *opts) +{ + opts->MaxSessions = DEFAULT_MAX_SESSIONS; + opts->MaxConnectionsPerSession = DEFAULT_MAX_CONNECTIONS_PER_SESSION; + opts->MaxQueueDepth = DEFAULT_MAX_QUEUE_DEPTH; + opts->DefaultTime2Wait = DEFAULT_DEFAULTTIME2WAIT; + opts->DefaultTime2Retain = DEFAULT_DEFAULTTIME2RETAIN; + opts->FirstBurstLength = SPDK_ISCSI_FIRST_BURST_LENGTH; + opts->ImmediateData = DEFAULT_IMMEDIATEDATA; + opts->AllowDuplicateIsid = false; + opts->ErrorRecoveryLevel = DEFAULT_ERRORRECOVERYLEVEL; + opts->timeout = DEFAULT_TIMEOUT; + opts->nopininterval = DEFAULT_NOPININTERVAL; + opts->disable_chap = false; + opts->require_chap = false; + opts->mutual_chap = false; + opts->chap_group = 0; + opts->authfile = NULL; + opts->nodebase = NULL; +} + +struct spdk_iscsi_opts * +iscsi_opts_alloc(void) +{ + struct spdk_iscsi_opts *opts; + + opts = calloc(1, sizeof(*opts)); + if (!opts) { + SPDK_ERRLOG("calloc() failed for iscsi options\n"); + return NULL; + } + + iscsi_opts_init(opts); + + return opts; +} + +void +iscsi_opts_free(struct spdk_iscsi_opts *opts) +{ + free(opts->authfile); + free(opts->nodebase); + free(opts); +} + +/* Deep copy of spdk_iscsi_opts */ +struct spdk_iscsi_opts * +iscsi_opts_copy(struct spdk_iscsi_opts *src) +{ + struct spdk_iscsi_opts *dst; + + dst = calloc(1, sizeof(*dst)); + if (!dst) { + SPDK_ERRLOG("calloc() failed for iscsi options\n"); + return NULL; + } + + if (src->authfile) { + dst->authfile = strdup(src->authfile); + if (!dst->authfile) { + free(dst); + SPDK_ERRLOG("failed to strdup for auth file %s\n", src->authfile); + return NULL; + } + } + + if (src->nodebase) { + dst->nodebase = strdup(src->nodebase); + if (!dst->nodebase) { + free(dst->authfile); + free(dst); + SPDK_ERRLOG("failed to strdup for nodebase %s\n", src->nodebase); + return NULL; + } + } + + dst->MaxSessions = src->MaxSessions; + dst->MaxConnectionsPerSession = src->MaxConnectionsPerSession; + dst->MaxQueueDepth = src->MaxQueueDepth; + dst->DefaultTime2Wait = src->DefaultTime2Wait; + dst->DefaultTime2Retain = src->DefaultTime2Retain; + dst->FirstBurstLength = src->FirstBurstLength; + dst->ImmediateData = src->ImmediateData; + dst->AllowDuplicateIsid = src->AllowDuplicateIsid; + dst->ErrorRecoveryLevel = src->ErrorRecoveryLevel; + dst->timeout = src->timeout; + dst->nopininterval = src->nopininterval; + dst->disable_chap = src->disable_chap; + dst->require_chap = src->require_chap; + dst->mutual_chap = src->mutual_chap; + dst->chap_group = src->chap_group; + + return dst; +} + +static int +iscsi_read_config_file_params(struct spdk_conf_section *sp, + struct spdk_iscsi_opts *opts) +{ + const char *val; + int MaxSessions; + int MaxConnectionsPerSession; + int MaxQueueDepth; + int DefaultTime2Wait; + int DefaultTime2Retain; + int FirstBurstLength; + int ErrorRecoveryLevel; + int timeout; + int nopininterval; + const char *ag_tag; + int ag_tag_i; + int i; + + val = spdk_conf_section_get_val(sp, "Comment"); + if (val != NULL) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Comment %s\n", val); + } + + val = spdk_conf_section_get_val(sp, "AuthFile"); + if (val != NULL) { + opts->authfile = strdup(val); + if (!opts->authfile) { + SPDK_ERRLOG("strdup() failed for AuthFile\n"); + return -ENOMEM; + } + } + + val = spdk_conf_section_get_val(sp, "NodeBase"); + if (val != NULL) { + opts->nodebase = strdup(val); + if (!opts->nodebase) { + free(opts->authfile); + SPDK_ERRLOG("strdup() failed for NodeBase\n"); + return -ENOMEM; + } + } + + MaxSessions = spdk_conf_section_get_intval(sp, "MaxSessions"); + if (MaxSessions >= 0) { + opts->MaxSessions = MaxSessions; + } + + MaxConnectionsPerSession = spdk_conf_section_get_intval(sp, "MaxConnectionsPerSession"); + if (MaxConnectionsPerSession >= 0) { + opts->MaxConnectionsPerSession = MaxConnectionsPerSession; + } + + MaxQueueDepth = spdk_conf_section_get_intval(sp, "MaxQueueDepth"); + if (MaxQueueDepth >= 0) { + opts->MaxQueueDepth = MaxQueueDepth; + } + + DefaultTime2Wait = spdk_conf_section_get_intval(sp, "DefaultTime2Wait"); + if (DefaultTime2Wait >= 0) { + opts->DefaultTime2Wait = DefaultTime2Wait; + } + + DefaultTime2Retain = spdk_conf_section_get_intval(sp, "DefaultTime2Retain"); + if (DefaultTime2Retain >= 0) { + opts->DefaultTime2Retain = DefaultTime2Retain; + } + + FirstBurstLength = spdk_conf_section_get_intval(sp, "FirstBurstLength"); + if (FirstBurstLength >= 0) { + opts->FirstBurstLength = FirstBurstLength; + } + + opts->ImmediateData = spdk_conf_section_get_boolval(sp, "ImmediateData", + opts->ImmediateData); + + /* This option is only for test. + * If AllowDuplicateIsid is enabled, it allows different connections carrying + * TSIH=0 login the target within the same session. + */ + opts->AllowDuplicateIsid = spdk_conf_section_get_boolval(sp, "AllowDuplicateIsid", + opts->AllowDuplicateIsid); + + ErrorRecoveryLevel = spdk_conf_section_get_intval(sp, "ErrorRecoveryLevel"); + if (ErrorRecoveryLevel >= 0) { + opts->ErrorRecoveryLevel = ErrorRecoveryLevel; + } + timeout = spdk_conf_section_get_intval(sp, "Timeout"); + if (timeout >= 0) { + opts->timeout = timeout; + } + nopininterval = spdk_conf_section_get_intval(sp, "NopInInterval"); + if (nopininterval >= 0) { + opts->nopininterval = nopininterval; + } + val = spdk_conf_section_get_val(sp, "DiscoveryAuthMethod"); + if (val != NULL) { + for (i = 0; ; i++) { + val = spdk_conf_section_get_nmval(sp, "DiscoveryAuthMethod", 0, i); + if (val == NULL) { + break; + } + if (strcasecmp(val, "CHAP") == 0) { + opts->require_chap = true; + } else if (strcasecmp(val, "Mutual") == 0) { + opts->require_chap = true; + opts->mutual_chap = true; + } else if (strcasecmp(val, "Auto") == 0) { + opts->disable_chap = false; + opts->require_chap = false; + opts->mutual_chap = false; + } else if (strcasecmp(val, "None") == 0) { + opts->disable_chap = true; + opts->require_chap = false; + opts->mutual_chap = false; + } else { + SPDK_ERRLOG("unknown CHAP mode %s\n", val); + } + } + if (opts->mutual_chap && !opts->require_chap) { + free(opts->authfile); + free(opts->nodebase); + SPDK_ERRLOG("CHAP must set to be required when using mutual CHAP.\n"); + return -EINVAL; + } + } + val = spdk_conf_section_get_val(sp, "DiscoveryAuthGroup"); + if (val != NULL) { + ag_tag = val; + if (strcasecmp(ag_tag, "None") == 0) { + opts->chap_group = 0; + } else { + if (strncasecmp(ag_tag, "AuthGroup", + strlen("AuthGroup")) != 0 + || sscanf(ag_tag, "%*[^0-9]%d", &ag_tag_i) != 1 + || ag_tag_i == 0) { + SPDK_ERRLOG("invalid auth group %s, ignoring\n", ag_tag); + } else { + opts->chap_group = ag_tag_i; + } + } + } + + return 0; +} + +static int +iscsi_opts_verify(struct spdk_iscsi_opts *opts) +{ + if (!opts->nodebase) { + opts->nodebase = strdup(SPDK_ISCSI_DEFAULT_NODEBASE); + if (opts->nodebase == NULL) { + SPDK_ERRLOG("strdup() failed for default nodebase\n"); + return -ENOMEM; + } + } + + if (opts->MaxSessions == 0 || opts->MaxSessions > 65535) { + SPDK_ERRLOG("%d is invalid. MaxSessions must be more than 0 and no more than 65535\n", + opts->MaxSessions); + return -EINVAL; + } + + if (opts->MaxConnectionsPerSession == 0 || opts->MaxConnectionsPerSession > 65535) { + SPDK_ERRLOG("%d is invalid. MaxConnectionsPerSession must be more than 0 and no more than 65535\n", + opts->MaxConnectionsPerSession); + return -EINVAL; + } + + if (opts->MaxQueueDepth == 0 || opts->MaxQueueDepth > 256) { + SPDK_ERRLOG("%d is invalid. MaxQueueDepth must be more than 0 and no more than 256\n", + opts->MaxQueueDepth); + return -EINVAL; + } + + if (opts->DefaultTime2Wait > 3600) { + SPDK_ERRLOG("%d is invalid. DefaultTime2Wait must be no more than 3600\n", + opts->DefaultTime2Wait); + return -EINVAL; + } + + if (opts->DefaultTime2Retain > 3600) { + SPDK_ERRLOG("%d is invalid. DefaultTime2Retain must be no more than 3600\n", + opts->DefaultTime2Retain); + return -EINVAL; + } + + if (opts->FirstBurstLength >= SPDK_ISCSI_MIN_FIRST_BURST_LENGTH) { + if (opts->FirstBurstLength > SPDK_ISCSI_MAX_BURST_LENGTH) { + SPDK_ERRLOG("FirstBurstLength %d shall not exceed MaxBurstLength %d\n", + opts->FirstBurstLength, SPDK_ISCSI_MAX_BURST_LENGTH); + return -EINVAL; + } + } else { + SPDK_ERRLOG("FirstBurstLength %d shall be no less than %d\n", + opts->FirstBurstLength, SPDK_ISCSI_MIN_FIRST_BURST_LENGTH); + return -EINVAL; + } + + if (opts->ErrorRecoveryLevel > 2) { + SPDK_ERRLOG("ErrorRecoveryLevel %d is not supported.\n", opts->ErrorRecoveryLevel); + return -EINVAL; + } + + if (opts->timeout < 0) { + SPDK_ERRLOG("%d is invalid. timeout must not be less than 0\n", opts->timeout); + return -EINVAL; + } + + if (opts->nopininterval < 0 || opts->nopininterval > MAX_NOPININTERVAL) { + SPDK_ERRLOG("%d is invalid. nopinterval must be between 0 and %d\n", + opts->nopininterval, MAX_NOPININTERVAL); + return -EINVAL; + } + + if (!iscsi_check_chap_params(opts->disable_chap, opts->require_chap, + opts->mutual_chap, opts->chap_group)) { + SPDK_ERRLOG("CHAP params in opts are illegal combination\n"); + return -EINVAL; + } + + return 0; +} + +static int +iscsi_parse_options(struct spdk_iscsi_opts **popts) +{ + struct spdk_iscsi_opts *opts; + struct spdk_conf_section *sp; + int rc; + + opts = iscsi_opts_alloc(); + if (!opts) { + SPDK_ERRLOG("iscsi_opts_alloc_failed() failed\n"); + return -ENOMEM; + } + + /* Process parameters */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "iscsi_read_config_file_parmas\n"); + sp = spdk_conf_find_section(NULL, "iSCSI"); + if (sp != NULL) { + rc = iscsi_read_config_file_params(sp, opts); + if (rc != 0) { + free(opts); + SPDK_ERRLOG("iscsi_read_config_file_params() failed\n"); + return rc; + } + } + + *popts = opts; + + return 0; +} + +static int +iscsi_set_global_params(struct spdk_iscsi_opts *opts) +{ + int rc; + + rc = iscsi_opts_verify(opts); + if (rc != 0) { + SPDK_ERRLOG("spdk_iscsi_opts_verify() failed\n"); + return rc; + } + + if (opts->authfile != NULL) { + g_iscsi.authfile = strdup(opts->authfile); + if (!g_iscsi.authfile) { + SPDK_ERRLOG("failed to strdup for auth file %s\n", opts->authfile); + return -ENOMEM; + } + } + + g_iscsi.nodebase = strdup(opts->nodebase); + if (!g_iscsi.nodebase) { + SPDK_ERRLOG("failed to strdup for nodebase %s\n", opts->nodebase); + return -ENOMEM; + } + + g_iscsi.MaxSessions = opts->MaxSessions; + g_iscsi.MaxConnectionsPerSession = opts->MaxConnectionsPerSession; + g_iscsi.MaxQueueDepth = opts->MaxQueueDepth; + g_iscsi.DefaultTime2Wait = opts->DefaultTime2Wait; + g_iscsi.DefaultTime2Retain = opts->DefaultTime2Retain; + g_iscsi.FirstBurstLength = opts->FirstBurstLength; + g_iscsi.ImmediateData = opts->ImmediateData; + g_iscsi.AllowDuplicateIsid = opts->AllowDuplicateIsid; + g_iscsi.ErrorRecoveryLevel = opts->ErrorRecoveryLevel; + g_iscsi.timeout = opts->timeout; + g_iscsi.nopininterval = opts->nopininterval; + g_iscsi.disable_chap = opts->disable_chap; + g_iscsi.require_chap = opts->require_chap; + g_iscsi.mutual_chap = opts->mutual_chap; + g_iscsi.chap_group = opts->chap_group; + + iscsi_log_globals(); + + return 0; +} + +int +iscsi_set_discovery_auth(bool disable_chap, bool require_chap, bool mutual_chap, + int32_t chap_group) +{ + if (!iscsi_check_chap_params(disable_chap, require_chap, mutual_chap, + chap_group)) { + SPDK_ERRLOG("CHAP params are illegal combination\n"); + return -EINVAL; + } + + pthread_mutex_lock(&g_iscsi.mutex); + g_iscsi.disable_chap = disable_chap; + g_iscsi.require_chap = require_chap; + g_iscsi.mutual_chap = mutual_chap; + g_iscsi.chap_group = chap_group; + pthread_mutex_unlock(&g_iscsi.mutex); + + return 0; +} + +int +iscsi_auth_group_add_secret(struct spdk_iscsi_auth_group *group, + const char *user, const char *secret, + const char *muser, const char *msecret) +{ + struct spdk_iscsi_auth_secret *_secret; + size_t len; + + if (user == NULL || secret == NULL) { + SPDK_ERRLOG("user and secret must be specified\n"); + return -EINVAL; + } + + if (muser != NULL && msecret == NULL) { + SPDK_ERRLOG("msecret must be specified with muser\n"); + return -EINVAL; + } + + TAILQ_FOREACH(_secret, &group->secret_head, tailq) { + if (strcmp(_secret->user, user) == 0) { + SPDK_ERRLOG("user for secret is duplicated\n"); + return -EEXIST; + } + } + + _secret = calloc(1, sizeof(*_secret)); + if (_secret == NULL) { + SPDK_ERRLOG("calloc() failed for CHAP secret\n"); + return -ENOMEM; + } + + len = strnlen(user, sizeof(_secret->user)); + if (len > sizeof(_secret->user) - 1) { + SPDK_ERRLOG("CHAP user longer than %zu characters: %s\n", + sizeof(_secret->user) - 1, user); + free(_secret); + return -EINVAL; + } + memcpy(_secret->user, user, len); + + len = strnlen(secret, sizeof(_secret->secret)); + if (len > sizeof(_secret->secret) - 1) { + SPDK_ERRLOG("CHAP secret longer than %zu characters: %s\n", + sizeof(_secret->secret) - 1, secret); + free(_secret); + return -EINVAL; + } + memcpy(_secret->secret, secret, len); + + if (muser != NULL) { + len = strnlen(muser, sizeof(_secret->muser)); + if (len > sizeof(_secret->muser) - 1) { + SPDK_ERRLOG("Mutual CHAP user longer than %zu characters: %s\n", + sizeof(_secret->muser) - 1, muser); + free(_secret); + return -EINVAL; + } + memcpy(_secret->muser, muser, len); + + len = strnlen(msecret, sizeof(_secret->msecret)); + if (len > sizeof(_secret->msecret) - 1) { + SPDK_ERRLOG("Mutual CHAP secret longer than %zu characters: %s\n", + sizeof(_secret->msecret) - 1, msecret); + free(_secret); + return -EINVAL; + } + memcpy(_secret->msecret, msecret, len); + } + + TAILQ_INSERT_TAIL(&group->secret_head, _secret, tailq); + return 0; +} + +int +iscsi_auth_group_delete_secret(struct spdk_iscsi_auth_group *group, + const char *user) +{ + struct spdk_iscsi_auth_secret *_secret; + + if (user == NULL) { + SPDK_ERRLOG("user must be specified\n"); + return -EINVAL; + } + + TAILQ_FOREACH(_secret, &group->secret_head, tailq) { + if (strcmp(_secret->user, user) == 0) { + break; + } + } + + if (_secret == NULL) { + SPDK_ERRLOG("secret is not found\n"); + return -ENODEV; + } + + TAILQ_REMOVE(&group->secret_head, _secret, tailq); + free(_secret); + + return 0; +} + +int +iscsi_add_auth_group(int32_t tag, struct spdk_iscsi_auth_group **_group) +{ + struct spdk_iscsi_auth_group *group; + + TAILQ_FOREACH(group, &g_iscsi.auth_group_head, tailq) { + if (group->tag == tag) { + SPDK_ERRLOG("Auth group (%d) already exists\n", tag); + return -EEXIST; + } + } + + group = calloc(1, sizeof(*group)); + if (group == NULL) { + SPDK_ERRLOG("calloc() failed for auth group\n"); + return -ENOMEM; + } + + TAILQ_INIT(&group->secret_head); + group->tag = tag; + + TAILQ_INSERT_TAIL(&g_iscsi.auth_group_head, group, tailq); + + *_group = group; + return 0; +} + +void +iscsi_delete_auth_group(struct spdk_iscsi_auth_group *group) +{ + struct spdk_iscsi_auth_secret *_secret, *tmp; + + TAILQ_REMOVE(&g_iscsi.auth_group_head, group, tailq); + + TAILQ_FOREACH_SAFE(_secret, &group->secret_head, tailq, tmp) { + TAILQ_REMOVE(&group->secret_head, _secret, tailq); + free(_secret); + } + free(group); +} + +struct spdk_iscsi_auth_group * +iscsi_find_auth_group_by_tag(int32_t tag) +{ + struct spdk_iscsi_auth_group *group; + + TAILQ_FOREACH(group, &g_iscsi.auth_group_head, tailq) { + if (group->tag == tag) { + return group; + } + } + + return NULL; +} + +static void +iscsi_auth_groups_destroy(void) +{ + struct spdk_iscsi_auth_group *group, *tmp; + + TAILQ_FOREACH_SAFE(group, &g_iscsi.auth_group_head, tailq, tmp) { + iscsi_delete_auth_group(group); + } +} + +static int +iscsi_parse_auth_group(struct spdk_conf_section *sp) +{ + int rc; + int i; + int tag; + const char *val, *user, *secret, *muser, *msecret; + struct spdk_iscsi_auth_group *group = NULL; + + val = spdk_conf_section_get_val(sp, "Comment"); + if (val != NULL) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Comment %s\n", val); + } + + tag = spdk_conf_section_get_num(sp); + + rc = iscsi_add_auth_group(tag, &group); + if (rc != 0) { + SPDK_ERRLOG("Failed to add auth group\n"); + return rc; + } + + for (i = 0; ; i++) { + val = spdk_conf_section_get_nval(sp, "Auth", i); + if (val == NULL) { + break; + } + + user = spdk_conf_section_get_nmval(sp, "Auth", i, 0); + secret = spdk_conf_section_get_nmval(sp, "Auth", i, 1); + muser = spdk_conf_section_get_nmval(sp, "Auth", i, 2); + msecret = spdk_conf_section_get_nmval(sp, "Auth", i, 3); + + rc = iscsi_auth_group_add_secret(group, user, secret, muser, msecret); + if (rc != 0) { + SPDK_ERRLOG("Failed to add secret to auth group\n"); + iscsi_delete_auth_group(group); + return rc; + } + } + + return 0; +} + +static int +iscsi_parse_auth_info(void) +{ + struct spdk_conf *config; + struct spdk_conf_section *sp; + int rc; + + config = spdk_conf_allocate(); + if (!config) { + SPDK_ERRLOG("Failed to allocate config file\n"); + return -ENOMEM; + } + + rc = spdk_conf_read(config, g_iscsi.authfile); + if (rc != 0) { + SPDK_INFOLOG(SPDK_LOG_ISCSI, "Failed to load auth file\n"); + spdk_conf_free(config); + return rc; + } + + sp = spdk_conf_first_section(config); + while (sp != NULL) { + if (spdk_conf_section_match_prefix(sp, "AuthGroup")) { + if (spdk_conf_section_get_num(sp) == 0) { + SPDK_ERRLOG("Group 0 is invalid\n"); + iscsi_auth_groups_destroy(); + spdk_conf_free(config); + return -EINVAL; + } + + rc = iscsi_parse_auth_group(sp); + if (rc != 0) { + SPDK_ERRLOG("parse_auth_group() failed\n"); + iscsi_auth_groups_destroy(); + spdk_conf_free(config); + return rc; + } + } + sp = spdk_conf_next_section(sp); + } + + spdk_conf_free(config); + return 0; +} + +static struct spdk_iscsi_auth_secret * +iscsi_find_auth_secret(const char *authuser, int ag_tag) +{ + struct spdk_iscsi_auth_group *group; + struct spdk_iscsi_auth_secret *_secret; + + TAILQ_FOREACH(group, &g_iscsi.auth_group_head, tailq) { + if (group->tag == ag_tag) { + TAILQ_FOREACH(_secret, &group->secret_head, tailq) { + if (strcmp(_secret->user, authuser) == 0) { + return _secret; + } + } + } + } + + return NULL; +} + +int +iscsi_chap_get_authinfo(struct iscsi_chap_auth *auth, const char *authuser, + int ag_tag) +{ + struct spdk_iscsi_auth_secret *_secret; + + if (authuser == NULL) { + return -EINVAL; + } + + if (auth->user[0] != '\0') { + memset(auth->user, 0, sizeof(auth->user)); + memset(auth->secret, 0, sizeof(auth->secret)); + memset(auth->muser, 0, sizeof(auth->muser)); + memset(auth->msecret, 0, sizeof(auth->msecret)); + } + + pthread_mutex_lock(&g_iscsi.mutex); + + _secret = iscsi_find_auth_secret(authuser, ag_tag); + if (_secret == NULL) { + pthread_mutex_unlock(&g_iscsi.mutex); + + SPDK_ERRLOG("CHAP secret is not found: user:%s, tag:%d\n", + authuser, ag_tag); + return -ENOENT; + } + + memcpy(auth->user, _secret->user, sizeof(auth->user)); + memcpy(auth->secret, _secret->secret, sizeof(auth->secret)); + + if (_secret->muser[0] != '\0') { + memcpy(auth->muser, _secret->muser, sizeof(auth->muser)); + memcpy(auth->msecret, _secret->msecret, sizeof(auth->msecret)); + } + + pthread_mutex_unlock(&g_iscsi.mutex); + return 0; +} + +static int +iscsi_initialize_global_params(void) +{ + int rc; + + if (!g_spdk_iscsi_opts) { + rc = iscsi_parse_options(&g_spdk_iscsi_opts); + if (rc != 0) { + SPDK_ERRLOG("iscsi_parse_options() failed\n"); + return rc; + } + } + + rc = iscsi_set_global_params(g_spdk_iscsi_opts); + if (rc != 0) { + SPDK_ERRLOG("iscsi_set_global_params() failed\n"); + } + + iscsi_opts_free(g_spdk_iscsi_opts); + g_spdk_iscsi_opts = NULL; + + return rc; +} + +static void +iscsi_init_complete(int rc) +{ + spdk_iscsi_init_cb cb_fn = g_init_cb_fn; + void *cb_arg = g_init_cb_arg; + + g_init_cb_fn = NULL; + g_init_cb_arg = NULL; + + cb_fn(cb_arg, rc); +} + +static void +iscsi_parse_configuration(void) +{ + int rc; + + rc = iscsi_parse_portal_grps(); + if (rc < 0) { + SPDK_ERRLOG("iscsi_parse_portal_grps() failed\n"); + goto end; + } + + rc = iscsi_parse_init_grps(); + if (rc < 0) { + SPDK_ERRLOG("iscsi_parse_init_grps() failed\n"); + goto end; + } + + rc = iscsi_parse_tgt_nodes(); + if (rc < 0) { + SPDK_ERRLOG("iscsi_parse_tgt_nodes() failed\n"); + } + + if (g_iscsi.authfile != NULL) { + if (access(g_iscsi.authfile, R_OK) == 0) { + rc = iscsi_parse_auth_info(); + if (rc < 0) { + SPDK_ERRLOG("iscsi_parse_auth_info() failed\n"); + } + } else { + SPDK_INFOLOG(SPDK_LOG_ISCSI, "CHAP secret file is not found in the path %s\n", + g_iscsi.authfile); + } + } + +end: + iscsi_init_complete(rc); +} + +static int +iscsi_poll_group_poll(void *ctx) +{ + struct spdk_iscsi_poll_group *group = ctx; + struct spdk_iscsi_conn *conn, *tmp; + int rc; + + if (spdk_unlikely(STAILQ_EMPTY(&group->connections))) { + return SPDK_POLLER_IDLE; + } + + rc = spdk_sock_group_poll(group->sock_group); + if (rc < 0) { + SPDK_ERRLOG("Failed to poll sock_group=%p\n", group->sock_group); + } + + STAILQ_FOREACH_SAFE(conn, &group->connections, pg_link, tmp) { + if (conn->state == ISCSI_CONN_STATE_EXITING) { + iscsi_conn_destruct(conn); + } + } + + return rc != 0 ? SPDK_POLLER_BUSY : SPDK_POLLER_IDLE; +} + +static int +iscsi_poll_group_handle_nop(void *ctx) +{ + struct spdk_iscsi_poll_group *group = ctx; + struct spdk_iscsi_conn *conn, *tmp; + + STAILQ_FOREACH_SAFE(conn, &group->connections, pg_link, tmp) { + iscsi_conn_handle_nop(conn); + } + + return SPDK_POLLER_BUSY; +} + +static int +iscsi_poll_group_create(void *io_device, void *ctx_buf) +{ + struct spdk_iscsi_poll_group *pg = ctx_buf; + + STAILQ_INIT(&pg->connections); + pg->sock_group = spdk_sock_group_create(NULL); + assert(pg->sock_group != NULL); + + pg->poller = SPDK_POLLER_REGISTER(iscsi_poll_group_poll, pg, 0); + /* set the period to 1 sec */ + pg->nop_poller = SPDK_POLLER_REGISTER(iscsi_poll_group_handle_nop, pg, 1000000); + + return 0; +} + +static void +iscsi_poll_group_destroy(void *io_device, void *ctx_buf) +{ + struct spdk_iscsi_poll_group *pg = ctx_buf; + struct spdk_io_channel *ch; + struct spdk_thread *thread; + + assert(pg->poller != NULL); + assert(pg->sock_group != NULL); + + spdk_sock_group_close(&pg->sock_group); + spdk_poller_unregister(&pg->poller); + spdk_poller_unregister(&pg->nop_poller); + + ch = spdk_io_channel_from_ctx(pg); + thread = spdk_io_channel_get_thread(ch); + + assert(thread == spdk_get_thread()); + + spdk_thread_exit(thread); +} + +static void +_iscsi_init_thread_done(void *ctx) +{ + struct spdk_iscsi_poll_group *pg = ctx; + + TAILQ_INSERT_TAIL(&g_iscsi.poll_group_head, pg, link); + if (--g_iscsi.refcnt == 0) { + iscsi_parse_configuration(); + } +} + +static void +_iscsi_init_thread(void *ctx) +{ + struct spdk_io_channel *ch; + struct spdk_iscsi_poll_group *pg; + + ch = spdk_get_io_channel(&g_iscsi); + pg = spdk_io_channel_get_ctx(ch); + + spdk_thread_send_msg(g_init_thread, _iscsi_init_thread_done, pg); +} + +static void +initialize_iscsi_poll_group(void) +{ + struct spdk_cpuset tmp_cpumask = {}; + uint32_t i; + char thread_name[32]; + struct spdk_thread *thread; + + spdk_io_device_register(&g_iscsi, iscsi_poll_group_create, iscsi_poll_group_destroy, + sizeof(struct spdk_iscsi_poll_group), "iscsi_tgt"); + + /* Create threads for CPU cores active for this application, and send a + * message to each thread to create a poll group on it. + */ + g_init_thread = spdk_get_thread(); + assert(g_init_thread != NULL); + assert(g_iscsi.refcnt == 0); + + SPDK_ENV_FOREACH_CORE(i) { + spdk_cpuset_zero(&tmp_cpumask); + spdk_cpuset_set_cpu(&tmp_cpumask, i, true); + snprintf(thread_name, sizeof(thread_name), "iscsi_poll_group_%u", i); + + thread = spdk_thread_create(thread_name, &tmp_cpumask); + assert(thread != NULL); + + g_iscsi.refcnt++; + spdk_thread_send_msg(thread, _iscsi_init_thread, NULL); + } +} + +static int +iscsi_parse_globals(void) +{ + int rc; + + rc = iscsi_initialize_global_params(); + if (rc != 0) { + SPDK_ERRLOG("iscsi_initialize_iscsi_global_params() failed\n"); + return rc; + } + + g_iscsi.session = calloc(1, sizeof(struct spdk_iscsi_sess *) * g_iscsi.MaxSessions); + if (!g_iscsi.session) { + SPDK_ERRLOG("calloc() failed for session array\n"); + return -1; + } + + /* + * For now, just support same number of total connections, rather + * than MaxSessions * MaxConnectionsPerSession. After we add better + * handling for low resource conditions from our various buffer + * pools, we can bump this up to support more connections. + */ + g_iscsi.MaxConnections = g_iscsi.MaxSessions; + + rc = iscsi_initialize_all_pools(); + if (rc != 0) { + SPDK_ERRLOG("initialize_all_pools() failed\n"); + free(g_iscsi.session); + g_iscsi.session = NULL; + return -1; + } + + rc = initialize_iscsi_conns(); + if (rc < 0) { + SPDK_ERRLOG("initialize_iscsi_conns() failed\n"); + free(g_iscsi.session); + g_iscsi.session = NULL; + return rc; + } + + initialize_iscsi_poll_group(); + return 0; +} + +void +spdk_iscsi_init(spdk_iscsi_init_cb cb_fn, void *cb_arg) +{ + int rc; + + assert(cb_fn != NULL); + g_init_cb_fn = cb_fn; + g_init_cb_arg = cb_arg; + + rc = iscsi_parse_globals(); + if (rc < 0) { + SPDK_ERRLOG("iscsi_parse_globals() failed\n"); + iscsi_init_complete(-1); + } + + /* + * iscsi_parse_configuration() will be called as the callback to + * spdk_initialize_iscsi_poll_group() and will complete iSCSI + * subsystem initialization. + */ +} + +void +spdk_iscsi_fini(spdk_iscsi_fini_cb cb_fn, void *cb_arg) +{ + g_fini_cb_fn = cb_fn; + g_fini_cb_arg = cb_arg; + + iscsi_portal_grp_close_all(); + shutdown_iscsi_conns(); +} + +static void +iscsi_fini_done(void *io_device) +{ + free(g_iscsi.authfile); + free(g_iscsi.nodebase); + + pthread_mutex_destroy(&g_iscsi.mutex); + g_fini_cb_fn(g_fini_cb_arg); +} + +static void +_iscsi_fini_dev_unreg(struct spdk_io_channel_iter *i, int status) +{ + iscsi_check_pools(); + iscsi_free_pools(); + free(g_iscsi.session); + + assert(TAILQ_EMPTY(&g_iscsi.poll_group_head)); + + iscsi_shutdown_tgt_nodes(); + iscsi_init_grps_destroy(); + iscsi_portal_grps_destroy(); + iscsi_auth_groups_destroy(); + + spdk_io_device_unregister(&g_iscsi, iscsi_fini_done); +} + +static void +_iscsi_fini_thread(struct spdk_io_channel_iter *i) +{ + struct spdk_io_channel *ch; + struct spdk_iscsi_poll_group *pg; + + ch = spdk_io_channel_iter_get_channel(i); + pg = spdk_io_channel_get_ctx(ch); + + pthread_mutex_lock(&g_iscsi.mutex); + TAILQ_REMOVE(&g_iscsi.poll_group_head, pg, link); + pthread_mutex_unlock(&g_iscsi.mutex); + + spdk_put_io_channel(ch); + + spdk_for_each_channel_continue(i, 0); +} + +void +shutdown_iscsi_conns_done(void) +{ + spdk_for_each_channel(&g_iscsi, _iscsi_fini_thread, NULL, _iscsi_fini_dev_unreg); +} + +void +spdk_iscsi_config_text(FILE *fp) +{ + iscsi_globals_config_text(fp); + iscsi_portal_grps_config_text(fp); + iscsi_init_grps_config_text(fp); + iscsi_tgt_nodes_config_text(fp); +} + +void +iscsi_opts_info_json(struct spdk_json_write_ctx *w) +{ + spdk_json_write_object_begin(w); + + if (g_iscsi.authfile != NULL) { + spdk_json_write_named_string(w, "auth_file", g_iscsi.authfile); + } + spdk_json_write_named_string(w, "node_base", g_iscsi.nodebase); + + spdk_json_write_named_uint32(w, "max_sessions", g_iscsi.MaxSessions); + spdk_json_write_named_uint32(w, "max_connections_per_session", + g_iscsi.MaxConnectionsPerSession); + + spdk_json_write_named_uint32(w, "max_queue_depth", g_iscsi.MaxQueueDepth); + + spdk_json_write_named_uint32(w, "default_time2wait", g_iscsi.DefaultTime2Wait); + spdk_json_write_named_uint32(w, "default_time2retain", g_iscsi.DefaultTime2Retain); + + spdk_json_write_named_uint32(w, "first_burst_length", g_iscsi.FirstBurstLength); + + spdk_json_write_named_bool(w, "immediate_data", g_iscsi.ImmediateData); + + spdk_json_write_named_bool(w, "allow_duplicated_isid", g_iscsi.AllowDuplicateIsid); + + spdk_json_write_named_uint32(w, "error_recovery_level", g_iscsi.ErrorRecoveryLevel); + + spdk_json_write_named_int32(w, "nop_timeout", g_iscsi.timeout); + spdk_json_write_named_int32(w, "nop_in_interval", g_iscsi.nopininterval); + + spdk_json_write_named_bool(w, "disable_chap", g_iscsi.disable_chap); + spdk_json_write_named_bool(w, "require_chap", g_iscsi.require_chap); + spdk_json_write_named_bool(w, "mutual_chap", g_iscsi.mutual_chap); + spdk_json_write_named_int32(w, "chap_group", g_iscsi.chap_group); + + spdk_json_write_object_end(w); +} + +static void +iscsi_auth_group_info_json(struct spdk_iscsi_auth_group *group, + struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_auth_secret *_secret; + + spdk_json_write_object_begin(w); + + spdk_json_write_named_int32(w, "tag", group->tag); + + spdk_json_write_named_array_begin(w, "secrets"); + TAILQ_FOREACH(_secret, &group->secret_head, tailq) { + spdk_json_write_object_begin(w); + + spdk_json_write_named_string(w, "user", _secret->user); + spdk_json_write_named_string(w, "secret", _secret->secret); + + if (_secret->muser[0] != '\0') { + spdk_json_write_named_string(w, "muser", _secret->muser); + spdk_json_write_named_string(w, "msecret", _secret->msecret); + } + + spdk_json_write_object_end(w); + } + spdk_json_write_array_end(w); + + spdk_json_write_object_end(w); +} + +static void +iscsi_auth_group_config_json(struct spdk_iscsi_auth_group *group, + struct spdk_json_write_ctx *w) +{ + spdk_json_write_object_begin(w); + + spdk_json_write_named_string(w, "method", "iscsi_create_auth_group"); + + spdk_json_write_name(w, "params"); + iscsi_auth_group_info_json(group, w); + + spdk_json_write_object_end(w); +} + +void +iscsi_auth_groups_info_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_auth_group *group; + + TAILQ_FOREACH(group, &g_iscsi.auth_group_head, tailq) { + iscsi_auth_group_info_json(group, w); + } +} + +static void +iscsi_auth_groups_config_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_auth_group *group; + + TAILQ_FOREACH(group, &g_iscsi.auth_group_head, tailq) { + iscsi_auth_group_config_json(group, w); + } +} + +static void +iscsi_opts_config_json(struct spdk_json_write_ctx *w) +{ + spdk_json_write_object_begin(w); + + spdk_json_write_named_string(w, "method", "iscsi_set_options"); + + spdk_json_write_name(w, "params"); + iscsi_opts_info_json(w); + + spdk_json_write_object_end(w); +} + +void +spdk_iscsi_config_json(struct spdk_json_write_ctx *w) +{ + spdk_json_write_array_begin(w); + iscsi_opts_config_json(w); + iscsi_portal_grps_config_json(w); + iscsi_init_grps_config_json(w); + iscsi_tgt_nodes_config_json(w); + iscsi_auth_groups_config_json(w); + spdk_json_write_array_end(w); +} + +SPDK_LOG_REGISTER_COMPONENT("iscsi", SPDK_LOG_ISCSI) diff --git a/src/spdk/lib/iscsi/md5.c b/src/spdk/lib/iscsi/md5.c new file mode 100644 index 000000000..c316ac354 --- /dev/null +++ b/src/spdk/lib/iscsi/md5.c @@ -0,0 +1,75 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include <openssl/md5.h> + +#include "iscsi/md5.h" + +int md5init(struct spdk_md5ctx *md5ctx) +{ + int rc; + + if (md5ctx == NULL) { + return -1; + } + rc = MD5_Init(&md5ctx->md5ctx); + return rc; +} + +int md5final(void *md5, struct spdk_md5ctx *md5ctx) +{ + int rc; + + if (md5ctx == NULL || md5 == NULL) { + return -1; + } + rc = MD5_Final(md5, &md5ctx->md5ctx); + return rc; +} + +int md5update(struct spdk_md5ctx *md5ctx, const void *data, size_t len) +{ + int rc; + + if (md5ctx == NULL) { + return -1; + } + if (data == NULL || len == 0) { + return 0; + } + rc = MD5_Update(&md5ctx->md5ctx, data, len); + return rc; +} diff --git a/src/spdk/lib/iscsi/md5.h b/src/spdk/lib/iscsi/md5.h new file mode 100644 index 000000000..d6fc4c1ff --- /dev/null +++ b/src/spdk/lib/iscsi/md5.h @@ -0,0 +1,52 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SPDK_MD5_H +#define SPDK_MD5_H + +#include "spdk/stdinc.h" + +#include <openssl/md5.h> + +#define SPDK_MD5DIGEST_LEN MD5_DIGEST_LENGTH + +struct spdk_md5ctx { + MD5_CTX md5ctx; +}; + +int md5init(struct spdk_md5ctx *md5ctx); +int md5final(void *md5, struct spdk_md5ctx *md5ctx); +int md5update(struct spdk_md5ctx *md5ctx, const void *data, size_t len); + +#endif /* SPDK_MD5_H */ diff --git a/src/spdk/lib/iscsi/param.c b/src/spdk/lib/iscsi/param.c new file mode 100644 index 000000000..18f579359 --- /dev/null +++ b/src/spdk/lib/iscsi/param.c @@ -0,0 +1,1216 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include "spdk/string.h" +#include "iscsi/iscsi.h" +#include "iscsi/param.h" +#include "iscsi/conn.h" +#include "spdk/string.h" + +#include "spdk_internal/log.h" + +#define MAX_TMPBUF 1024 + +/* whose value may be bigger than 255 */ +static const char *non_simple_value_params[] = { + "CHAP_C", + "CHAP_R", + NULL, +}; + +void +iscsi_param_free(struct iscsi_param *params) +{ + struct iscsi_param *param, *next_param; + + if (params == NULL) { + return; + } + for (param = params; param != NULL; param = next_param) { + next_param = param->next; + if (param->list) { + free(param->list); + } + free(param->val); + free(param->key); + free(param); + } +} + +static int +iscsi_find_key_in_array(const char *key, const char *array[]) +{ + int i; + + for (i = 0; array[i] != NULL; i++) { + if (strcasecmp(key, array[i]) == 0) { + return 1; + } + } + return 0; +} + +struct iscsi_param * +iscsi_param_find(struct iscsi_param *params, const char *key) +{ + struct iscsi_param *param; + + if (params == NULL || key == NULL) { + return NULL; + } + for (param = params; param != NULL; param = param->next) { + if (param->key != NULL && param->key[0] == key[0] + && strcasecmp(param->key, key) == 0) { + return param; + } + } + return NULL; +} + +int +iscsi_param_del(struct iscsi_param **params, const char *key) +{ + struct iscsi_param *param, *prev_param = NULL; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "del %s\n", key); + if (params == NULL || key == NULL) { + return 0; + } + for (param = *params; param != NULL; param = param->next) { + if (param->key != NULL && param->key[0] == key[0] + && strcasecmp(param->key, key) == 0) { + if (prev_param != NULL) { + prev_param->next = param->next; + } else { + *params = param->next; + } + param->next = NULL; + iscsi_param_free(param); + return 0; + } + prev_param = param; + } + return -1; +} + +int +iscsi_param_add(struct iscsi_param **params, const char *key, + const char *val, const char *list, int type) +{ + struct iscsi_param *param, *last_param; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "add %s=%s, list=[%s], type=%d\n", + key, val, list, type); + if (key == NULL) { + return -1; + } + + param = iscsi_param_find(*params, key); + if (param != NULL) { + iscsi_param_del(params, key); + } + + param = calloc(1, sizeof(*param)); + if (!param) { + SPDK_ERRLOG("calloc() failed for parameter\n"); + return -ENOMEM; + } + + param->next = NULL; + param->key = xstrdup(key); + param->val = xstrdup(val); + param->list = xstrdup(list); + param->type = type; + + last_param = *params; + if (last_param != NULL) { + while (last_param->next != NULL) { + last_param = last_param->next; + } + last_param->next = param; + } else { + *params = param; + } + + return 0; +} + +int +iscsi_param_set(struct iscsi_param *params, const char *key, + const char *val) +{ + struct iscsi_param *param; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "set %s=%s\n", key, val); + param = iscsi_param_find(params, key); + if (param == NULL) { + SPDK_ERRLOG("no key %s\n", key); + return -1; + } + + free(param->val); + + param->val = xstrdup(val); + + return 0; +} + +int +iscsi_param_set_int(struct iscsi_param *params, const char *key, uint32_t val) +{ + char buf[MAX_TMPBUF]; + struct iscsi_param *param; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "set %s=%d\n", key, val); + param = iscsi_param_find(params, key); + if (param == NULL) { + SPDK_ERRLOG("no key %s\n", key); + return -1; + } + + free(param->val); + snprintf(buf, sizeof buf, "%d", val); + + param->val = strdup(buf); + + return 0; +} + +/** + * Parse a single KEY=VAL pair + * + * data = "KEY=VAL<NUL>" + */ +static int +iscsi_parse_param(struct iscsi_param **params, const uint8_t *data, uint32_t data_len) +{ + int rc; + uint8_t *key_copy, *val_copy; + const uint8_t *key_end; + int key_len, val_len; + int max_len; + + data_len = strnlen(data, data_len); + /* No such thing as strnchr so use memchr instead. */ + key_end = memchr(data, '=', data_len); + if (!key_end) { + SPDK_ERRLOG("'=' not found\n"); + return -1; + } + + key_len = key_end - data; + if (key_len == 0) { + SPDK_ERRLOG("Empty key\n"); + return -1; + } + /* + * RFC 7143 6.1 + */ + if (key_len > ISCSI_TEXT_MAX_KEY_LEN) { + SPDK_ERRLOG("Key name length is bigger than 63\n"); + return -1; + } + + key_copy = malloc(key_len + 1); + if (!key_copy) { + SPDK_ERRLOG("malloc() failed for key_copy\n"); + return -ENOMEM; + } + + memcpy(key_copy, data, key_len); + key_copy[key_len] = '\0'; + /* check whether this key is duplicated */ + if (NULL != iscsi_param_find(*params, key_copy)) { + SPDK_ERRLOG("Duplicated Key %s\n", key_copy); + free(key_copy); + return -1; + } + + val_len = strnlen(key_end + 1, data_len - key_len - 1); + /* + * RFC 3720 5.1 + * If not otherwise specified, the maximum length of a simple-value + * (not its encoded representation) is 255 bytes, not including the delimiter + * (comma or zero byte). + */ + /* + * comma or zero is counted in, otherwise we need to iterate each parameter + * value + */ + max_len = iscsi_find_key_in_array(key_copy, non_simple_value_params) ? + ISCSI_TEXT_MAX_VAL_LEN : ISCSI_TEXT_MAX_SIMPLE_VAL_LEN; + if (val_len > max_len) { + SPDK_ERRLOG("Overflow Val %d\n", val_len); + free(key_copy); + return -1; + } + + val_copy = calloc(1, val_len + 1); + if (val_copy == NULL) { + SPDK_ERRLOG("Could not allocate value string\n"); + free(key_copy); + return -1; + } + + memcpy(val_copy, key_end + 1, val_len); + + rc = iscsi_param_add(params, key_copy, val_copy, NULL, 0); + free(val_copy); + free(key_copy); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_add() failed\n"); + return -1; + } + + /* return number of bytes consumed + * +1 for '=' and +1 for NUL + */ + return key_len + 1 + val_len + 1; +} + +/** + * Parse a sequence of KEY=VAL pairs. + * + * \param data "KEY=VAL<NUL>KEY=VAL<NUL>..." + * \param len length of data in bytes + */ +int +iscsi_parse_params(struct iscsi_param **params, const uint8_t *data, + int len, bool cbit_enabled, char **partial_parameter) +{ + int rc, offset = 0; + char *p; + int i; + + /* strip the partial text parameters if previous PDU have C enabled */ + if (partial_parameter && *partial_parameter) { + for (i = 0; i < len && data[i] != '\0'; i++) { + ; + } + p = spdk_sprintf_alloc("%s%s", *partial_parameter, (const char *)data); + if (!p) { + return -1; + } + rc = iscsi_parse_param(params, p, i + strlen(*partial_parameter)); + free(p); + if (rc < 0) { + return -1; + } + free(*partial_parameter); + *partial_parameter = NULL; + + data = data + i + 1; + len = len - (i + 1); + } + + /* strip the partial text parameters if C bit is enabled */ + if (cbit_enabled) { + if (partial_parameter == NULL) { + SPDK_ERRLOG("C bit set but no partial parameters provided\n"); + return -1; + } + + /* + * reverse iterate the string from the tail not including '\0' + */ + for (i = len - 1; data[i] != '\0' && i > 0; i--) { + ; + } + if (i != 0) { + /* We found a NULL character - don't copy it into the + * partial parameter. + */ + i++; + } + + *partial_parameter = calloc(1, len - i + 1); + if (*partial_parameter == NULL) { + SPDK_ERRLOG("could not allocate partial parameter\n"); + return -1; + } + memcpy(*partial_parameter, &data[i], len - i); + if (i == 0) { + /* No full parameters to parse - so return now. */ + return 0; + } else { + len = i - 1; + } + } + + while (offset < len && data[offset] != '\0') { + rc = iscsi_parse_param(params, data + offset, len - offset); + if (rc < 0) { + return -1; + } + offset += rc; + } + return 0; +} + +char * +iscsi_param_get_val(struct iscsi_param *params, const char *key) +{ + struct iscsi_param *param; + + param = iscsi_param_find(params, key); + if (param == NULL) { + return NULL; + } + return param->val; +} + +int +iscsi_param_eq_val(struct iscsi_param *params, const char *key, + const char *val) +{ + struct iscsi_param *param; + + param = iscsi_param_find(params, key); + if (param == NULL) { + return 0; + } + if (strcasecmp(param->val, val) == 0) { + return 1; + } + return 0; +} + +struct iscsi_param_table { + const char *key; + const char *val; + const char *list; + int type; +}; + +static const struct iscsi_param_table conn_param_table[] = { + { "HeaderDigest", "None", "CRC32C,None", ISPT_LIST }, + { "DataDigest", "None", "CRC32C,None", ISPT_LIST }, + { "MaxRecvDataSegmentLength", "8192", "512,16777215", ISPT_NUMERICAL_DECLARATIVE }, + { "OFMarker", "No", "Yes,No", ISPT_BOOLEAN_AND }, + { "IFMarker", "No", "Yes,No", ISPT_BOOLEAN_AND }, + { "OFMarkInt", "1", "1,65535", ISPT_NUMERICAL_MIN }, + { "IFMarkInt", "1", "1,65535", ISPT_NUMERICAL_MIN }, + { "AuthMethod", "None", "CHAP,None", ISPT_LIST }, + { "CHAP_A", "5", "5", ISPT_LIST }, + { "CHAP_N", "", "", ISPT_DECLARATIVE }, + { "CHAP_R", "", "", ISPT_DECLARATIVE }, + { "CHAP_I", "", "", ISPT_DECLARATIVE }, + { "CHAP_C", "", "", ISPT_DECLARATIVE }, + { NULL, NULL, NULL, ISPT_INVALID }, +}; + +static const struct iscsi_param_table sess_param_table[] = { + { "MaxConnections", "1", "1,65535", ISPT_NUMERICAL_MIN }, +#if 0 + /* need special handling */ + { "SendTargets", "", "", ISPT_DECLARATIVE }, +#endif + { "TargetName", "", "", ISPT_DECLARATIVE }, + { "InitiatorName", "", "", ISPT_DECLARATIVE }, + { "TargetAlias", "", "", ISPT_DECLARATIVE }, + { "InitiatorAlias", "", "", ISPT_DECLARATIVE }, + { "TargetAddress", "", "", ISPT_DECLARATIVE }, + { "TargetPortalGroupTag", "1", "1,65535", ISPT_NUMERICAL_DECLARATIVE }, + { "InitialR2T", "Yes", "Yes,No", ISPT_BOOLEAN_OR }, + { "ImmediateData", "Yes", "Yes,No", ISPT_BOOLEAN_AND }, + { "MaxBurstLength", "262144", "512,16777215", ISPT_NUMERICAL_MIN }, + { "FirstBurstLength", "65536", "512,16777215", ISPT_NUMERICAL_MIN }, + { "DefaultTime2Wait", "2", "0,3600", ISPT_NUMERICAL_MAX }, + { "DefaultTime2Retain", "20", "0,3600", ISPT_NUMERICAL_MIN }, + { "MaxOutstandingR2T", "1", "1,65536", ISPT_NUMERICAL_MIN }, + { "DataPDUInOrder", "Yes", "Yes,No", ISPT_BOOLEAN_OR }, + { "DataSequenceInOrder", "Yes", "Yes,No", ISPT_BOOLEAN_OR }, + { "ErrorRecoveryLevel", "0", "0,2", ISPT_NUMERICAL_MIN }, + { "SessionType", "Normal", "Normal,Discovery", ISPT_DECLARATIVE }, + { NULL, NULL, NULL, ISPT_INVALID }, +}; + +static int +iscsi_params_init_internal(struct iscsi_param **params, + const struct iscsi_param_table *table) +{ + int rc; + int i; + struct iscsi_param *param; + + for (i = 0; table[i].key != NULL; i++) { + rc = iscsi_param_add(params, table[i].key, table[i].val, + table[i].list, table[i].type); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_add() failed\n"); + return -1; + } + param = iscsi_param_find(*params, table[i].key); + if (param != NULL) { + param->state_index = i; + } else { + SPDK_ERRLOG("iscsi_param_find() failed\n"); + return -1; + } + } + + return 0; +} + +int +iscsi_conn_params_init(struct iscsi_param **params) +{ + return iscsi_params_init_internal(params, &conn_param_table[0]); +} + +int +iscsi_sess_params_init(struct iscsi_param **params) +{ + return iscsi_params_init_internal(params, &sess_param_table[0]); +} + +static const char *chap_type[] = { + "CHAP_A", + "CHAP_N", + "CHAP_R", + "CHAP_I", + "CHAP_C", + NULL, +}; + +static const char *discovery_ignored_param[] = { + "MaxConnections", + "InitialR2T", + "ImmediateData", + "MaxBurstLength", + "FirstBurstLength" + "MaxOutstandingR2T", + "DataPDUInOrder", + "DataSequenceInOrder", + NULL, +}; + +static const char *multi_negot_conn_params[] = { + "MaxRecvDataSegmentLength", + NULL, +}; + +/* The following params should be declared by target */ +static const char *target_declarative_params[] = { + "TargetAlias", + "TargetAddress", + "TargetPortalGroupTag", + NULL, +}; + +/* This function is used to construct the data from the special param (e.g., + * MaxRecvDataSegmentLength) + * return: + * normal: the total len of the data + * error: -1 + */ +static int +iscsi_special_param_construction(struct spdk_iscsi_conn *conn, + struct iscsi_param *param, + bool FirstBurstLength_flag, char *data, + int alloc_len, int total) +{ + int len; + struct iscsi_param *param_first; + struct iscsi_param *param_max; + uint32_t FirstBurstLength; + uint32_t MaxBurstLength; + char *val; + + val = malloc(ISCSI_TEXT_MAX_VAL_LEN + 1); + if (!val) { + SPDK_ERRLOG("malloc() failed for temporary buffer\n"); + return -ENOMEM; + } + + if (strcasecmp(param->key, "MaxRecvDataSegmentLength") == 0) { + /* + * MaxRecvDataSegmentLength is sent by both + * initiator and target, but is declarative - meaning + * each direction can have different values. + * So when MaxRecvDataSegmentLength is found in the + * the parameter set sent from the initiator, add SPDK + * iscsi target's MaxRecvDataSegmentLength value to + * the returned parameter list. + */ + if (alloc_len - total < 1) { + SPDK_ERRLOG("data space small %d\n", alloc_len); + free(val); + return -1; + } + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "returning MaxRecvDataSegmentLength=%d\n", + SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH); + len = snprintf((char *)data + total, alloc_len - total, + "MaxRecvDataSegmentLength=%d", + SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH); + total += len + 1; + } + + if (strcasecmp(param->key, "MaxBurstLength") == 0 && + !FirstBurstLength_flag) { + if (alloc_len - total < 1) { + SPDK_ERRLOG("data space small %d\n", alloc_len); + free(val); + return -1; + } + + param_first = iscsi_param_find(conn->sess->params, + "FirstBurstLength"); + if (param_first != NULL) { + FirstBurstLength = (uint32_t)strtol(param_first->val, NULL, 10); + } else { + FirstBurstLength = SPDK_ISCSI_FIRST_BURST_LENGTH; + } + param_max = iscsi_param_find(conn->sess->params, + "MaxBurstLength"); + if (param_max != NULL) { + MaxBurstLength = (uint32_t)strtol(param_max->val, NULL, 10); + } else { + MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH; + } + + if (FirstBurstLength > MaxBurstLength) { + FirstBurstLength = MaxBurstLength; + if (param_first != NULL) { + free(param_first->val); + snprintf(val, ISCSI_TEXT_MAX_VAL_LEN, "%d", + FirstBurstLength); + param_first->val = xstrdup(val); + } + } + len = snprintf((char *)data + total, alloc_len - total, + "FirstBurstLength=%d", FirstBurstLength); + total += len + 1; + } + + free(val); + return total; + +} + +/** + * iscsi_construct_data_from_param: + * To construct the data which will be returned to the initiator + * return: length of the negotiated data, -1 indicates error; + */ +static int +iscsi_construct_data_from_param(struct iscsi_param *param, char *new_val, + char *data, int alloc_len, int total) +{ + int len; + + if (param->type != ISPT_DECLARATIVE && + param->type != ISPT_NUMERICAL_DECLARATIVE) { + if (alloc_len - total < 1) { + SPDK_ERRLOG("data space small %d\n", alloc_len); + return -1; + } + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "negotiated %s=%s\n", + param->key, new_val); + len = snprintf((char *)data + total, alloc_len - total, "%s=%s", + param->key, new_val); + total += len + 1; + } + return total; +} + +/** + * To negotiate param with + * type = ISPT_LIST + * return: the negotiated value of the key + */ +static char * +iscsi_negotiate_param_list(int *add_param_value, + struct iscsi_param *param, + char *valid_list, char *in_val, + char *cur_val) +{ + char *val_start, *val_end; + char *in_start, *in_end; + int flag = 0; + + if (add_param_value == NULL) { + return NULL; + } + + in_start = in_val; + do { + if ((in_end = strchr(in_start, (int)',')) != NULL) { + *in_end = '\0'; + } + val_start = valid_list; + do { + if ((val_end = strchr(val_start, (int)',')) != NULL) { + *val_end = '\0'; + } + if (strcasecmp(in_start, val_start) == 0) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "match %s\n", + val_start); + flag = 1; + break; + } + if (val_end) { + *val_end = ','; + val_start = val_end + 1; + } + } while (val_end); + if (flag) { + break; + } + if (in_end) { + *in_end = ','; + in_start = in_end + 1; + } + } while (in_end); + + return flag ? val_start : NULL; +} + +/** + * To negotiate param with + * type = ISPT_NUMERICAL_MIN/MAX, ISPT_NUMERICAL_DECLARATIVE + * return: the negotiated value of the key + */ +static char * +iscsi_negotiate_param_numerical(int *add_param_value, + struct iscsi_param *param, + char *valid_list, char *in_val, + char *cur_val) +{ + char *valid_next; + char *new_val = NULL; + char *min_val, *max_val; + int val_i, cur_val_i; + int min_i, max_i; + + if (add_param_value == NULL) { + return NULL; + } + + val_i = (int)strtol(param->val, NULL, 10); + /* check whether the key is FirstBurstLength, if that we use in_val */ + if (strcasecmp(param->key, "FirstBurstLength") == 0) { + val_i = (int)strtol(in_val, NULL, 10); + } + + cur_val_i = (int)strtol(cur_val, NULL, 10); + valid_next = valid_list; + min_val = spdk_strsepq(&valid_next, ","); + max_val = spdk_strsepq(&valid_next, ","); + min_i = (min_val != NULL) ? (int)strtol(min_val, NULL, 10) : 0; + max_i = (max_val != NULL) ? (int)strtol(max_val, NULL, 10) : 0; + if (val_i < min_i || val_i > max_i) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "key %.64s reject\n", param->key); + new_val = NULL; + } else { + switch (param->type) { + case ISPT_NUMERICAL_MIN: + if (val_i > cur_val_i) { + val_i = cur_val_i; + } + break; + case ISPT_NUMERICAL_MAX: + if (val_i < cur_val_i) { + val_i = cur_val_i; + } + break; + default: + break; + } + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN, "%d", val_i); + new_val = in_val; + } + + return new_val; +} + +/** + * To negotiate param with + * type = ISPT_BOOLEAN_OR, ISPT_BOOLEAN_AND + * return: the negotiated value of the key + */ +static char * +iscsi_negotiate_param_boolean(int *add_param_value, + struct iscsi_param *param, + char *in_val, char *cur_val, + const char *value) +{ + char *new_val = NULL; + + if (add_param_value == NULL) { + return NULL; + } + + /* Make sure the val is Yes or No */ + if (!((strcasecmp(in_val, "Yes") == 0) || + (strcasecmp(in_val, "No") == 0))) { + /* unknown value */ + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN + 1, "%s", "Reject"); + new_val = in_val; + *add_param_value = 1; + return new_val; + } + + if (strcasecmp(cur_val, value) == 0) { + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN + 1, "%s", value); + new_val = in_val; + } else { + new_val = param->val; + } + + return new_val; +} + +/** + * The entry function to handle each type of the param + * return value: the new negotiated value + */ +static char * +iscsi_negotiate_param_all(int *add_param_value, struct iscsi_param *param, + char *valid_list, char *in_val, char *cur_val) +{ + char *new_val; + switch (param->type) { + case ISPT_LIST: + new_val = iscsi_negotiate_param_list(add_param_value, + param, + valid_list, + in_val, + cur_val); + break; + + case ISPT_NUMERICAL_MIN: + case ISPT_NUMERICAL_MAX: + case ISPT_NUMERICAL_DECLARATIVE: + new_val = iscsi_negotiate_param_numerical(add_param_value, + param, + valid_list, + in_val, + cur_val); + break; + + case ISPT_BOOLEAN_OR: + new_val = iscsi_negotiate_param_boolean(add_param_value, + param, + in_val, + cur_val, + "Yes"); + break; + case ISPT_BOOLEAN_AND: + new_val = iscsi_negotiate_param_boolean(add_param_value, + param, + in_val, + cur_val, + "No"); + break; + + default: + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN + 1, "%s", param->val); + new_val = in_val; + break; + } + + return new_val; +} + +/** + * This function is used to judge whether the param is in session's params or + * connection's params + */ +static int +iscsi_negotiate_param_init(struct spdk_iscsi_conn *conn, + struct iscsi_param **cur_param_p, + struct iscsi_param **params_dst_p, + struct iscsi_param *param) +{ + int index; + + *cur_param_p = iscsi_param_find(*params_dst_p, param->key); + if (*cur_param_p == NULL) { + *params_dst_p = conn->sess->params; + *cur_param_p = iscsi_param_find(*params_dst_p, param->key); + if (*cur_param_p == NULL) { + if ((strncasecmp(param->key, "X-", 2) == 0) || + (strncasecmp(param->key, "X#", 2) == 0)) { + /* Extension Key */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "extension key %.64s\n", + param->key); + } else { + SPDK_ERRLOG("unknown key %.64s\n", param->key); + } + return 1; + } else { + index = (*cur_param_p)->state_index; + if (conn->sess_param_state_negotiated[index] && + !iscsi_find_key_in_array(param->key, + target_declarative_params)) { + return SPDK_ISCSI_PARAMETER_EXCHANGE_NOT_ONCE; + } + conn->sess_param_state_negotiated[index] = true; + } + } else { + index = (*cur_param_p)->state_index; + if (conn->conn_param_state_negotiated[index] && + !iscsi_find_key_in_array(param->key, + multi_negot_conn_params)) { + return SPDK_ISCSI_PARAMETER_EXCHANGE_NOT_ONCE; + } + conn->conn_param_state_negotiated[index] = true; + } + + return 0; +} + +int +iscsi_negotiate_params(struct spdk_iscsi_conn *conn, + struct iscsi_param **params, uint8_t *data, int alloc_len, + int data_len) +{ + struct iscsi_param *param; + struct iscsi_param *cur_param; + char *valid_list, *in_val; + char *cur_val; + char *new_val; + int discovery; + int total; + int rc; + uint32_t FirstBurstLength; + uint32_t MaxBurstLength; + bool FirstBurstLength_flag = false; + int type; + + total = data_len; + if (data_len < 0) { + assert(false); + return -EINVAL; + } + if (alloc_len < 1) { + return 0; + } + if (total > alloc_len) { + total = alloc_len; + data[total - 1] = '\0'; + return total; + } + + if (*params == NULL) { + /* no input */ + return total; + } + + /* discovery? */ + discovery = 0; + cur_param = iscsi_param_find(*params, "SessionType"); + if (cur_param == NULL) { + cur_param = iscsi_param_find(conn->sess->params, "SessionType"); + if (cur_param == NULL) { + /* no session type */ + } else { + if (strcasecmp(cur_param->val, "Discovery") == 0) { + discovery = 1; + } + } + } else { + if (strcasecmp(cur_param->val, "Discovery") == 0) { + discovery = 1; + } + } + + /* for temporary store */ + valid_list = malloc(ISCSI_TEXT_MAX_VAL_LEN + 1); + if (!valid_list) { + SPDK_ERRLOG("malloc() failed for valid_list\n"); + return -ENOMEM; + } + + in_val = malloc(ISCSI_TEXT_MAX_VAL_LEN + 1); + if (!in_val) { + SPDK_ERRLOG("malloc() failed for in_val\n"); + free(valid_list); + return -ENOMEM; + } + + cur_val = malloc(ISCSI_TEXT_MAX_VAL_LEN + 1); + if (!cur_val) { + SPDK_ERRLOG("malloc() failed for cur_val\n"); + free(valid_list); + free(in_val); + return -ENOMEM; + } + + /* To adjust the location of FirstBurstLength location and put it to + * the end, then we can always firstly determine the MaxBurstLength + */ + param = iscsi_param_find(*params, "MaxBurstLength"); + if (param != NULL) { + param = iscsi_param_find(*params, "FirstBurstLength"); + + /* check the existence of FirstBurstLength */ + if (param != NULL) { + FirstBurstLength_flag = true; + if (param->next != NULL) { + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN + 1, "%s", param->val); + type = param->type; + iscsi_param_add(params, "FirstBurstLength", + in_val, NULL, type); + } + } + } + + for (param = *params; param != NULL; param = param->next) { + struct iscsi_param *params_dst = conn->params; + int add_param_value = 0; + new_val = NULL; + param->type = ISPT_INVALID; + + /* sendtargets is special */ + if (strcasecmp(param->key, "SendTargets") == 0) { + continue; + } + /* CHAP keys */ + if (iscsi_find_key_in_array(param->key, chap_type)) { + continue; + } + + /* 12.2, 12.10, 12.11, 12.13, 12.14, 12.17, 12.18, 12.19 */ + if (discovery && + iscsi_find_key_in_array(param->key, discovery_ignored_param)) { + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN + 1, "%s", "Irrelevant"); + new_val = in_val; + add_param_value = 1; + } else { + rc = iscsi_negotiate_param_init(conn, + &cur_param, + ¶ms_dst, + param); + if (rc < 0) { + free(valid_list); + free(in_val); + free(cur_val); + return rc; + } else if (rc > 0) { + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN + 1, "%s", "NotUnderstood"); + new_val = in_val; + add_param_value = 1; + } else { + snprintf(valid_list, ISCSI_TEXT_MAX_VAL_LEN + 1, "%s", cur_param->list); + snprintf(cur_val, ISCSI_TEXT_MAX_VAL_LEN + 1, "%s", cur_param->val); + param->type = cur_param->type; + } + } + + if (param->type > 0) { + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN + 1, "%s", param->val); + + /* "NotUnderstood" value shouldn't be assigned to "Understood" key */ + if (strcasecmp(in_val, "NotUnderstood") == 0) { + free(in_val); + free(valid_list); + free(cur_val); + return SPDK_ISCSI_LOGIN_ERROR_PARAMETER; + } + + if (strcasecmp(param->key, "FirstBurstLength") == 0) { + FirstBurstLength = (uint32_t)strtol(param->val, NULL, + 10); + new_val = iscsi_param_get_val(conn->sess->params, + "MaxBurstLength"); + if (new_val != NULL) { + MaxBurstLength = (uint32_t) strtol(new_val, NULL, + 10); + } else { + MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH; + } + if (FirstBurstLength < SPDK_ISCSI_MAX_FIRST_BURST_LENGTH && + FirstBurstLength > MaxBurstLength) { + FirstBurstLength = MaxBurstLength; + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN, "%d", + FirstBurstLength); + } + } + + /* prevent target's declarative params from being changed by initiator */ + if (iscsi_find_key_in_array(param->key, target_declarative_params)) { + add_param_value = 1; + } + + new_val = iscsi_negotiate_param_all(&add_param_value, + param, + valid_list, + in_val, + cur_val); + } + + /* check the negotiated value of the key */ + if (new_val != NULL) { + /* add_param_value = 0 means updating the value of + * existed key in the connection's parameters + */ + if (add_param_value == 0) { + iscsi_param_set(params_dst, param->key, new_val); + } + total = iscsi_construct_data_from_param(param, + new_val, + data, + alloc_len, + total); + if (total < 0) { + goto final_return; + } + + total = iscsi_special_param_construction(conn, + param, + FirstBurstLength_flag, + data, + alloc_len, + total); + if (total < 0) { + goto final_return; + } + } else { + total = -1; + break; + } + } + +final_return: + free(valid_list); + free(in_val); + free(cur_val); + + return total; +} + +int +iscsi_copy_param2var(struct spdk_iscsi_conn *conn) +{ + const char *val; + + val = iscsi_param_get_val(conn->params, "MaxRecvDataSegmentLength"); + if (val == NULL) { + SPDK_ERRLOG("Getval MaxRecvDataSegmentLength failed\n"); + return -1; + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "copy MaxRecvDataSegmentLength=%s\n", val); + conn->MaxRecvDataSegmentLength = (int)strtol(val, NULL, 10); + if (conn->MaxRecvDataSegmentLength > SPDK_BDEV_LARGE_BUF_MAX_SIZE) { + conn->MaxRecvDataSegmentLength = SPDK_BDEV_LARGE_BUF_MAX_SIZE; + } + + val = iscsi_param_get_val(conn->params, "HeaderDigest"); + if (val == NULL) { + SPDK_ERRLOG("Getval HeaderDigest failed\n"); + return -1; + } + if (strcasecmp(val, "CRC32C") == 0) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "set HeaderDigest=1\n"); + conn->header_digest = 1; + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "set HeaderDigest=0\n"); + conn->header_digest = 0; + } + val = iscsi_param_get_val(conn->params, "DataDigest"); + if (val == NULL) { + SPDK_ERRLOG("Getval DataDigest failed\n"); + return -1; + } + if (strcasecmp(val, "CRC32C") == 0) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "set DataDigest=1\n"); + conn->data_digest = 1; + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "set DataDigest=0\n"); + conn->data_digest = 0; + } + + val = iscsi_param_get_val(conn->sess->params, "MaxConnections"); + if (val == NULL) { + SPDK_ERRLOG("Getval MaxConnections failed\n"); + return -1; + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "copy MaxConnections=%s\n", val); + conn->sess->MaxConnections = (uint32_t) strtol(val, NULL, 10); + val = iscsi_param_get_val(conn->sess->params, "MaxOutstandingR2T"); + if (val == NULL) { + SPDK_ERRLOG("Getval MaxOutstandingR2T failed\n"); + return -1; + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "copy MaxOutstandingR2T=%s\n", val); + conn->sess->MaxOutstandingR2T = (uint32_t) strtol(val, NULL, 10); + val = iscsi_param_get_val(conn->sess->params, "FirstBurstLength"); + if (val == NULL) { + SPDK_ERRLOG("Getval FirstBurstLength failed\n"); + return -1; + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "copy FirstBurstLength=%s\n", val); + conn->sess->FirstBurstLength = (uint32_t) strtol(val, NULL, 10); + val = iscsi_param_get_val(conn->sess->params, "MaxBurstLength"); + if (val == NULL) { + SPDK_ERRLOG("Getval MaxBurstLength failed\n"); + return -1; + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "copy MaxBurstLength=%s\n", val); + conn->sess->MaxBurstLength = (uint32_t) strtol(val, NULL, 10); + val = iscsi_param_get_val(conn->sess->params, "InitialR2T"); + if (val == NULL) { + SPDK_ERRLOG("Getval InitialR2T failed\n"); + return -1; + } + if (strcasecmp(val, "Yes") == 0) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "set InitialR2T=1\n"); + conn->sess->InitialR2T = true; + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "set InitialR2T=0\n"); + conn->sess->InitialR2T = false; + } + val = iscsi_param_get_val(conn->sess->params, "ImmediateData"); + if (val == NULL) { + SPDK_ERRLOG("Getval ImmediateData failed\n"); + return -1; + } + if (strcasecmp(val, "Yes") == 0) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "set ImmediateData=1\n"); + conn->sess->ImmediateData = true; + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "set ImmediateData=0\n"); + conn->sess->ImmediateData = false; + } + return 0; +} diff --git a/src/spdk/lib/iscsi/param.h b/src/spdk/lib/iscsi/param.h new file mode 100644 index 000000000..ce194c514 --- /dev/null +++ b/src/spdk/lib/iscsi/param.h @@ -0,0 +1,94 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SPDK_ISCSI_PARAM_H +#define SPDK_ISCSI_PARAM_H + +#include "spdk/stdinc.h" + +struct spdk_iscsi_conn; + +enum iscsi_param_type { + ISPT_INVALID = -1, + ISPT_NOTSPECIFIED = 0, + ISPT_LIST, + ISPT_NUMERICAL_MIN, + ISPT_NUMERICAL_MAX, + ISPT_NUMERICAL_DECLARATIVE, + ISPT_DECLARATIVE, + ISPT_BOOLEAN_OR, + ISPT_BOOLEAN_AND, +}; + +struct iscsi_param { + struct iscsi_param *next; + char *key; + char *val; + char *list; + int type; + int state_index; +}; + +void +iscsi_param_free(struct iscsi_param *params); +struct iscsi_param * +iscsi_param_find(struct iscsi_param *params, const char *key); +int +iscsi_param_del(struct iscsi_param **params, const char *key); +int +iscsi_param_add(struct iscsi_param **params, const char *key, + const char *val, const char *list, int type); +int +iscsi_param_set(struct iscsi_param *params, const char *key, + const char *val); +int +iscsi_param_set_int(struct iscsi_param *params, const char *key, uint32_t val); +int +iscsi_parse_params(struct iscsi_param **params, const uint8_t *data, + int len, bool cbit_enabled, char **partial_parameter); +char * +iscsi_param_get_val(struct iscsi_param *params, const char *key); +int +iscsi_param_eq_val(struct iscsi_param *params, const char *key, + const char *val); + +int iscsi_negotiate_params(struct spdk_iscsi_conn *conn, + struct iscsi_param **params_p, uint8_t *data, + int alloc_len, int data_len); +int iscsi_copy_param2var(struct spdk_iscsi_conn *conn); + +int iscsi_conn_params_init(struct iscsi_param **params); +int iscsi_sess_params_init(struct iscsi_param **params); + +#endif /* SPDK_ISCSI_PARAM_H */ diff --git a/src/spdk/lib/iscsi/portal_grp.c b/src/spdk/lib/iscsi/portal_grp.c new file mode 100644 index 000000000..986562ad7 --- /dev/null +++ b/src/spdk/lib/iscsi/portal_grp.c @@ -0,0 +1,655 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include "spdk/conf.h" +#include "spdk/sock.h" +#include "spdk/string.h" + +#include "spdk_internal/log.h" + +#include "iscsi/iscsi.h" +#include "iscsi/conn.h" +#include "iscsi/portal_grp.h" +#include "iscsi/tgt_node.h" + +#define PORTNUMSTRLEN 32 +#define ACCEPT_TIMEOUT_US 1000 /* 1ms */ + +static int +iscsi_portal_accept(void *arg) +{ + struct spdk_iscsi_portal *portal = arg; + struct spdk_sock *sock; + int rc; + int count = 0; + + if (portal->sock == NULL) { + return -1; + } + + while (1) { + sock = spdk_sock_accept(portal->sock); + if (sock != NULL) { + rc = iscsi_conn_construct(portal, sock); + if (rc < 0) { + spdk_sock_close(&sock); + SPDK_ERRLOG("spdk_iscsi_connection_construct() failed\n"); + break; + } + count++; + } else { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + SPDK_ERRLOG("accept error(%d): %s\n", errno, spdk_strerror(errno)); + } + break; + } + } + + return count; +} + +static struct spdk_iscsi_portal * +iscsi_portal_find_by_addr(const char *host, const char *port) +{ + struct spdk_iscsi_portal *p; + + TAILQ_FOREACH(p, &g_iscsi.portal_head, g_tailq) { + if (!strcmp(p->host, host) && !strcmp(p->port, port)) { + return p; + } + } + + return NULL; +} + +/* Assumes caller allocated host and port strings on the heap */ +struct spdk_iscsi_portal * +iscsi_portal_create(const char *host, const char *port) +{ + struct spdk_iscsi_portal *p = NULL, *tmp; + + assert(host != NULL); + assert(port != NULL); + + if (strlen(host) > MAX_PORTAL_ADDR || strlen(port) > MAX_PORTAL_PORT) { + return NULL; + } + + p = calloc(1, sizeof(*p)); + if (!p) { + SPDK_ERRLOG("calloc() failed for portal\n"); + return NULL; + } + + /* check and overwrite abbreviation of wildcard */ + if (strcasecmp(host, "[*]") == 0) { + SPDK_WARNLOG("Please use \"[::]\" as IPv6 wildcard\n"); + SPDK_WARNLOG("Convert \"[*]\" to \"[::]\" automatically\n"); + SPDK_WARNLOG("(Use of \"[*]\" will be deprecated in a future release)"); + snprintf(p->host, sizeof(p->host), "[::]"); + } else if (strcasecmp(host, "*") == 0) { + SPDK_WARNLOG("Please use \"0.0.0.0\" as IPv4 wildcard\n"); + SPDK_WARNLOG("Convert \"*\" to \"0.0.0.0\" automatically\n"); + SPDK_WARNLOG("(Use of \"[*]\" will be deprecated in a future release)"); + snprintf(p->host, sizeof(p->host), "0.0.0.0"); + } else { + memcpy(p->host, host, strlen(host)); + } + + memcpy(p->port, port, strlen(port)); + + p->sock = NULL; + p->group = NULL; /* set at a later time by caller */ + p->acceptor_poller = NULL; + + pthread_mutex_lock(&g_iscsi.mutex); + tmp = iscsi_portal_find_by_addr(host, port); + if (tmp != NULL) { + pthread_mutex_unlock(&g_iscsi.mutex); + SPDK_ERRLOG("portal (%s, %s) already exists\n", host, port); + goto error_out; + } + + TAILQ_INSERT_TAIL(&g_iscsi.portal_head, p, g_tailq); + pthread_mutex_unlock(&g_iscsi.mutex); + + return p; + +error_out: + free(p); + + return NULL; +} + +void +iscsi_portal_destroy(struct spdk_iscsi_portal *p) +{ + assert(p != NULL); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "iscsi_portal_destroy\n"); + + pthread_mutex_lock(&g_iscsi.mutex); + TAILQ_REMOVE(&g_iscsi.portal_head, p, g_tailq); + pthread_mutex_unlock(&g_iscsi.mutex); + + free(p); + +} + +static int +iscsi_portal_open(struct spdk_iscsi_portal *p) +{ + struct spdk_sock *sock; + int port; + + if (p->sock != NULL) { + SPDK_ERRLOG("portal (%s, %s) is already opened\n", + p->host, p->port); + return -1; + } + + port = (int)strtol(p->port, NULL, 0); + sock = spdk_sock_listen(p->host, port, NULL); + if (sock == NULL) { + SPDK_ERRLOG("listen error %.64s.%d\n", p->host, port); + return -1; + } + + p->sock = sock; + + /* + * When the portal is created by config file, incoming connection + * requests for the socket are pended to accept until reactors start. + * However the gap between listen() and accept() will be slight and + * the requests will be queued by the nonzero backlog of the socket + * or resend by TCP. + */ + p->acceptor_poller = SPDK_POLLER_REGISTER(iscsi_portal_accept, p, ACCEPT_TIMEOUT_US); + + return 0; +} + +static void +iscsi_portal_close(struct spdk_iscsi_portal *p) +{ + if (p->sock) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "close portal (%s, %s)\n", + p->host, p->port); + spdk_poller_unregister(&p->acceptor_poller); + spdk_sock_close(&p->sock); + } +} + +static int +iscsi_parse_portal(const char *portalstring, struct spdk_iscsi_portal **ip) +{ + char *host = NULL, *port = NULL; + int len, rc = -1; + const char *p; + + if (portalstring == NULL) { + SPDK_ERRLOG("portal error\n"); + goto error_out; + } + + /* IP address */ + if (portalstring[0] == '[') { + /* IPv6 */ + p = strchr(portalstring + 1, ']'); + if (p == NULL) { + SPDK_ERRLOG("portal error\n"); + goto error_out; + } + p++; + } else { + /* IPv4 */ + p = strchr(portalstring, ':'); + if (p == NULL) { + p = portalstring + strlen(portalstring); + } + } + + len = p - portalstring; + host = malloc(len + 1); + if (host == NULL) { + SPDK_ERRLOG("malloc() failed for host\n"); + goto error_out; + } + memcpy(host, portalstring, len); + host[len] = '\0'; + + /* Port number (IPv4 and IPv6 are the same) */ + if (p[0] == '\0') { + port = malloc(PORTNUMSTRLEN); + if (!port) { + SPDK_ERRLOG("malloc() failed for port\n"); + goto error_out; + } + snprintf(port, PORTNUMSTRLEN, "%d", DEFAULT_PORT); + } else { + p++; + len = strlen(p); + port = malloc(len + 1); + if (port == NULL) { + SPDK_ERRLOG("malloc() failed for port\n"); + goto error_out; + } + memcpy(port, p, len); + port[len] = '\0'; + } + + *ip = iscsi_portal_create(host, port); + if (!*ip) { + goto error_out; + } + + rc = 0; +error_out: + free(host); + free(port); + + return rc; +} + +struct spdk_iscsi_portal_grp * +iscsi_portal_grp_create(int tag) +{ + struct spdk_iscsi_portal_grp *pg = malloc(sizeof(*pg)); + + if (!pg) { + SPDK_ERRLOG("malloc() failed for portal group\n"); + return NULL; + } + + pg->ref = 0; + pg->tag = tag; + + pthread_mutex_lock(&g_iscsi.mutex); + pg->disable_chap = g_iscsi.disable_chap; + pg->require_chap = g_iscsi.require_chap; + pg->mutual_chap = g_iscsi.mutual_chap; + pg->chap_group = g_iscsi.chap_group; + pthread_mutex_unlock(&g_iscsi.mutex); + + TAILQ_INIT(&pg->head); + + return pg; +} + +void +iscsi_portal_grp_destroy(struct spdk_iscsi_portal_grp *pg) +{ + struct spdk_iscsi_portal *p; + + assert(pg != NULL); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "iscsi_portal_grp_destroy\n"); + while (!TAILQ_EMPTY(&pg->head)) { + p = TAILQ_FIRST(&pg->head); + TAILQ_REMOVE(&pg->head, p, per_pg_tailq); + iscsi_portal_destroy(p); + } + free(pg); +} + +int +iscsi_portal_grp_register(struct spdk_iscsi_portal_grp *pg) +{ + int rc = -1; + struct spdk_iscsi_portal_grp *tmp; + + assert(pg != NULL); + + pthread_mutex_lock(&g_iscsi.mutex); + tmp = iscsi_portal_grp_find_by_tag(pg->tag); + if (tmp == NULL) { + TAILQ_INSERT_TAIL(&g_iscsi.pg_head, pg, tailq); + rc = 0; + } + pthread_mutex_unlock(&g_iscsi.mutex); + return rc; +} + +void +iscsi_portal_grp_add_portal(struct spdk_iscsi_portal_grp *pg, + struct spdk_iscsi_portal *p) +{ + assert(pg != NULL); + assert(p != NULL); + + p->group = pg; + TAILQ_INSERT_TAIL(&pg->head, p, per_pg_tailq); +} + +int +iscsi_portal_grp_set_chap_params(struct spdk_iscsi_portal_grp *pg, + bool disable_chap, bool require_chap, + bool mutual_chap, int32_t chap_group) +{ + if (!iscsi_check_chap_params(disable_chap, require_chap, + mutual_chap, chap_group)) { + return -EINVAL; + } + + pg->disable_chap = disable_chap; + pg->require_chap = require_chap; + pg->mutual_chap = mutual_chap; + pg->chap_group = chap_group; + + return 0; +} + +static int +iscsi_parse_portal_grp(struct spdk_conf_section *sp) +{ + struct spdk_iscsi_portal_grp *pg; + struct spdk_iscsi_portal *p; + const char *val; + char *label, *portal; + int i = 0, rc = 0; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "add portal group (from config file) %d\n", + spdk_conf_section_get_num(sp)); + + val = spdk_conf_section_get_val(sp, "Comment"); + if (val != NULL) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Comment %s\n", val); + } + + pg = iscsi_portal_grp_create(spdk_conf_section_get_num(sp)); + if (!pg) { + SPDK_ERRLOG("portal group malloc error (%s)\n", spdk_conf_section_get_name(sp)); + return -1; + } + + for (i = 0; ; i++) { + label = spdk_conf_section_get_nmval(sp, "Portal", i, 0); + portal = spdk_conf_section_get_nmval(sp, "Portal", i, 1); + if (label == NULL || portal == NULL) { + break; + } + + rc = iscsi_parse_portal(portal, &p); + if (rc < 0) { + SPDK_ERRLOG("parse portal error (%s)\n", portal); + goto error; + } + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "RIndex=%d, Host=%s, Port=%s, Tag=%d\n", + i, p->host, p->port, spdk_conf_section_get_num(sp)); + + iscsi_portal_grp_add_portal(pg, p); + } + + rc = iscsi_portal_grp_open(pg); + if (rc != 0) { + SPDK_ERRLOG("portal_grp_open failed\n"); + goto error; + } + + /* Add portal group to the end of the pg list */ + rc = iscsi_portal_grp_register(pg); + if (rc != 0) { + SPDK_ERRLOG("register portal failed\n"); + goto error; + } + + return 0; + +error: + iscsi_portal_grp_release(pg); + return -1; +} + +struct spdk_iscsi_portal_grp * +iscsi_portal_grp_find_by_tag(int tag) +{ + struct spdk_iscsi_portal_grp *pg; + + TAILQ_FOREACH(pg, &g_iscsi.pg_head, tailq) { + if (pg->tag == tag) { + return pg; + } + } + + return NULL; +} + +int +iscsi_parse_portal_grps(void) +{ + int rc = 0; + struct spdk_conf_section *sp; + + sp = spdk_conf_first_section(NULL); + while (sp != NULL) { + if (spdk_conf_section_match_prefix(sp, "PortalGroup")) { + if (spdk_conf_section_get_num(sp) == 0) { + SPDK_ERRLOG("Group 0 is invalid\n"); + return -1; + } + + /* Build portal group from cfg section PortalGroup */ + rc = iscsi_parse_portal_grp(sp); + if (rc < 0) { + SPDK_ERRLOG("parse_portal_group() failed\n"); + return -1; + } + } + sp = spdk_conf_next_section(sp); + } + return 0; +} + +void +iscsi_portal_grps_destroy(void) +{ + struct spdk_iscsi_portal_grp *pg; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "iscsi_portal_grps_destroy\n"); + pthread_mutex_lock(&g_iscsi.mutex); + while (!TAILQ_EMPTY(&g_iscsi.pg_head)) { + pg = TAILQ_FIRST(&g_iscsi.pg_head); + TAILQ_REMOVE(&g_iscsi.pg_head, pg, tailq); + pthread_mutex_unlock(&g_iscsi.mutex); + iscsi_portal_grp_destroy(pg); + pthread_mutex_lock(&g_iscsi.mutex); + } + pthread_mutex_unlock(&g_iscsi.mutex); +} + +int +iscsi_portal_grp_open(struct spdk_iscsi_portal_grp *pg) +{ + struct spdk_iscsi_portal *p; + int rc; + + TAILQ_FOREACH(p, &pg->head, per_pg_tailq) { + rc = iscsi_portal_open(p); + if (rc < 0) { + return rc; + } + } + return 0; +} + +static void +iscsi_portal_grp_close(struct spdk_iscsi_portal_grp *pg) +{ + struct spdk_iscsi_portal *p; + + TAILQ_FOREACH(p, &pg->head, per_pg_tailq) { + iscsi_portal_close(p); + } +} + +void +iscsi_portal_grp_close_all(void) +{ + struct spdk_iscsi_portal_grp *pg; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "iscsi_portal_grp_close_all\n"); + pthread_mutex_lock(&g_iscsi.mutex); + TAILQ_FOREACH(pg, &g_iscsi.pg_head, tailq) { + iscsi_portal_grp_close(pg); + } + pthread_mutex_unlock(&g_iscsi.mutex); +} + +struct spdk_iscsi_portal_grp * +iscsi_portal_grp_unregister(int tag) +{ + struct spdk_iscsi_portal_grp *pg; + + pthread_mutex_lock(&g_iscsi.mutex); + TAILQ_FOREACH(pg, &g_iscsi.pg_head, tailq) { + if (pg->tag == tag) { + TAILQ_REMOVE(&g_iscsi.pg_head, pg, tailq); + pthread_mutex_unlock(&g_iscsi.mutex); + return pg; + } + } + pthread_mutex_unlock(&g_iscsi.mutex); + return NULL; +} + +void +iscsi_portal_grp_release(struct spdk_iscsi_portal_grp *pg) +{ + iscsi_portal_grp_close(pg); + iscsi_portal_grp_destroy(pg); +} + +static const char *portal_group_section = \ + "\n" + "# Users must change the PortalGroup section(s) to match the IP addresses\n" + "# for their environment.\n" + "# PortalGroup sections define which network portals the iSCSI target\n" + "# will use to listen for incoming connections. These are also used to\n" + "# determine which targets are accessible over each portal group.\n" + "# Up to 1024 Portal directives are allowed. These define the network\n" + "# portals of the portal group. The user must specify a IP address\n" + "# for each network portal, and may optionally specify a port.\n" + "# If the port is omitted, 3260 will be used\n" + "# Syntax:\n" + "# Portal <Name> <IP address>[:<port>]\n"; + +#define PORTAL_GROUP_TMPL \ +"[PortalGroup%d]\n" \ +" Comment \"Portal%d\"\n" + +#define PORTAL_TMPL \ +" Portal DA1 %s:%s\n" + +void +iscsi_portal_grps_config_text(FILE *fp) +{ + struct spdk_iscsi_portal *p = NULL; + struct spdk_iscsi_portal_grp *pg = NULL; + + /* Create portal group section */ + fprintf(fp, "%s", portal_group_section); + + /* Dump portal groups */ + TAILQ_FOREACH(pg, &g_iscsi.pg_head, tailq) { + if (NULL == pg) { continue; } + fprintf(fp, PORTAL_GROUP_TMPL, pg->tag, pg->tag); + /* Dump portals */ + TAILQ_FOREACH(p, &pg->head, per_pg_tailq) { + if (NULL == p) { continue; } + fprintf(fp, PORTAL_TMPL, p->host, p->port); + } + } +} + +static void +iscsi_portal_grp_info_json(struct spdk_iscsi_portal_grp *pg, + struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_portal *portal; + + spdk_json_write_object_begin(w); + + spdk_json_write_named_int32(w, "tag", pg->tag); + + spdk_json_write_named_array_begin(w, "portals"); + TAILQ_FOREACH(portal, &pg->head, per_pg_tailq) { + spdk_json_write_object_begin(w); + + spdk_json_write_named_string(w, "host", portal->host); + spdk_json_write_named_string(w, "port", portal->port); + + spdk_json_write_object_end(w); + } + spdk_json_write_array_end(w); + + spdk_json_write_object_end(w); +} + +static void +iscsi_portal_grp_config_json(struct spdk_iscsi_portal_grp *pg, + struct spdk_json_write_ctx *w) +{ + spdk_json_write_object_begin(w); + + spdk_json_write_named_string(w, "method", "iscsi_create_portal_group"); + + spdk_json_write_name(w, "params"); + iscsi_portal_grp_info_json(pg, w); + + spdk_json_write_object_end(w); +} + +void +iscsi_portal_grps_info_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_portal_grp *pg; + + TAILQ_FOREACH(pg, &g_iscsi.pg_head, tailq) { + iscsi_portal_grp_info_json(pg, w); + } +} + +void +iscsi_portal_grps_config_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_portal_grp *pg; + + TAILQ_FOREACH(pg, &g_iscsi.pg_head, tailq) { + iscsi_portal_grp_config_json(pg, w); + } +} diff --git a/src/spdk/lib/iscsi/portal_grp.h b/src/spdk/lib/iscsi/portal_grp.h new file mode 100644 index 000000000..7ac72e36c --- /dev/null +++ b/src/spdk/lib/iscsi/portal_grp.h @@ -0,0 +1,90 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SPDK_PORTAL_GRP_H +#define SPDK_PORTAL_GRP_H + +#include "spdk/conf.h" +#include "spdk/cpuset.h" +#include "iscsi/iscsi.h" + +struct spdk_json_write_ctx; + +struct spdk_iscsi_portal { + struct spdk_iscsi_portal_grp *group; + char host[MAX_PORTAL_ADDR + 1]; + char port[MAX_PORTAL_PORT + 1]; + struct spdk_sock *sock; + struct spdk_poller *acceptor_poller; + TAILQ_ENTRY(spdk_iscsi_portal) per_pg_tailq; + TAILQ_ENTRY(spdk_iscsi_portal) g_tailq; +}; + +struct spdk_iscsi_portal_grp { + int ref; + int tag; + bool disable_chap; + bool require_chap; + bool mutual_chap; + int32_t chap_group; + TAILQ_ENTRY(spdk_iscsi_portal_grp) tailq; + TAILQ_HEAD(, spdk_iscsi_portal) head; +}; + +/* SPDK iSCSI Portal Group management API */ + +struct spdk_iscsi_portal *iscsi_portal_create(const char *host, const char *port); +void iscsi_portal_destroy(struct spdk_iscsi_portal *p); + +struct spdk_iscsi_portal_grp *iscsi_portal_grp_create(int tag); +void iscsi_portal_grp_add_portal(struct spdk_iscsi_portal_grp *pg, + struct spdk_iscsi_portal *p); +void iscsi_portal_grp_destroy(struct spdk_iscsi_portal_grp *pg); +void iscsi_portal_grp_release(struct spdk_iscsi_portal_grp *pg); +int iscsi_parse_portal_grps(void); +void iscsi_portal_grps_destroy(void); +int iscsi_portal_grp_register(struct spdk_iscsi_portal_grp *pg); +struct spdk_iscsi_portal_grp *iscsi_portal_grp_unregister(int tag); +struct spdk_iscsi_portal_grp *iscsi_portal_grp_find_by_tag(int tag); +int iscsi_portal_grp_open(struct spdk_iscsi_portal_grp *pg); +int iscsi_portal_grp_set_chap_params(struct spdk_iscsi_portal_grp *pg, + bool disable_chap, bool require_chap, + bool mutual_chap, int32_t chap_group); + +void iscsi_portal_grp_close_all(void); +void iscsi_portal_grps_config_text(FILE *fp); +void iscsi_portal_grps_info_json(struct spdk_json_write_ctx *w); +void iscsi_portal_grps_config_json(struct spdk_json_write_ctx *w); + +#endif /* SPDK_PORTAL_GRP_H */ diff --git a/src/spdk/lib/iscsi/spdk_iscsi.map b/src/spdk/lib/iscsi/spdk_iscsi.map new file mode 100644 index 000000000..0475a800d --- /dev/null +++ b/src/spdk/lib/iscsi/spdk_iscsi.map @@ -0,0 +1,11 @@ +{ + global: + + # Functions used by other SPDK libraries + spdk_iscsi_init; + spdk_iscsi_fini; + spdk_iscsi_config_text; + spdk_iscsi_config_json; + + local: *; +}; diff --git a/src/spdk/lib/iscsi/task.c b/src/spdk/lib/iscsi/task.c new file mode 100644 index 000000000..964621178 --- /dev/null +++ b/src/spdk/lib/iscsi/task.c @@ -0,0 +1,98 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/env.h" +#include "spdk/log.h" +#include "iscsi/conn.h" +#include "iscsi/task.h" + +static void +iscsi_task_free(struct spdk_scsi_task *scsi_task) +{ + struct spdk_iscsi_task *task = iscsi_task_from_scsi_task(scsi_task); + + if (task->parent) { + if (task->scsi.dxfer_dir == SPDK_SCSI_DIR_FROM_DEV) { + assert(task->conn->data_in_cnt > 0); + task->conn->data_in_cnt--; + } + + spdk_scsi_task_put(&task->parent->scsi); + task->parent = NULL; + } + + iscsi_task_disassociate_pdu(task); + assert(task->conn->pending_task_cnt > 0); + task->conn->pending_task_cnt--; + spdk_mempool_put(g_iscsi.task_pool, (void *)task); +} + +struct spdk_iscsi_task * +iscsi_task_get(struct spdk_iscsi_conn *conn, struct spdk_iscsi_task *parent, + spdk_scsi_task_cpl cpl_fn) +{ + struct spdk_iscsi_task *task; + + task = spdk_mempool_get(g_iscsi.task_pool); + if (!task) { + SPDK_ERRLOG("Unable to get task\n"); + abort(); + } + + assert(conn != NULL); + memset(task, 0, sizeof(*task)); + task->conn = conn; + assert(conn->pending_task_cnt < UINT32_MAX); + conn->pending_task_cnt++; + spdk_scsi_task_construct(&task->scsi, + cpl_fn, + iscsi_task_free); + if (parent) { + parent->scsi.ref++; + task->parent = parent; + task->tag = parent->tag; + task->lun_id = parent->lun_id; + task->scsi.dxfer_dir = parent->scsi.dxfer_dir; + task->scsi.transfer_len = parent->scsi.transfer_len; + task->scsi.lun = parent->scsi.lun; + task->scsi.cdb = parent->scsi.cdb; + task->scsi.target_port = parent->scsi.target_port; + task->scsi.initiator_port = parent->scsi.initiator_port; + if (task->scsi.dxfer_dir == SPDK_SCSI_DIR_FROM_DEV) { + conn->data_in_cnt++; + } + } + + return task; +} diff --git a/src/spdk/lib/iscsi/task.h b/src/spdk/lib/iscsi/task.h new file mode 100644 index 000000000..0ef48599a --- /dev/null +++ b/src/spdk/lib/iscsi/task.h @@ -0,0 +1,188 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SPDK_ISCSI_TASK_H +#define SPDK_ISCSI_TASK_H + +#include "iscsi/iscsi.h" +#include "spdk/scsi.h" +#include "spdk/util.h" + +struct spdk_iscsi_task { + struct spdk_scsi_task scsi; + + struct spdk_iscsi_task *parent; + + uint8_t rsp_scsi_status; + uint8_t rsp_sense_data[32]; + size_t rsp_sense_data_len; + + struct spdk_iscsi_conn *conn; + struct spdk_iscsi_pdu *pdu; + uint32_t outstanding_r2t; + + uint32_t desired_data_transfer_length; + + /* Only valid for Read/Write */ + uint32_t bytes_completed; + + uint32_t data_out_cnt; + + /* + * Tracks the current offset of large read io. + */ + uint32_t current_datain_offset; + + /* + * next_expected_r2t_offset is used when we receive + * the DataOUT PDU. + */ + uint32_t next_expected_r2t_offset; + + /* + * Tracks the length of the R2T that is in progress. + * Used to check that an R2T burst does not exceed + * MaxBurstLength. + */ + uint32_t current_r2t_length; + + /* + * next_r2t_offset is used when we are sending the + * R2T packet to keep track of next offset of r2t. + */ + uint32_t next_r2t_offset; + uint32_t R2TSN; + uint32_t r2t_datasn; /* record next datasn for a r2tsn */ + uint32_t acked_r2tsn; /* next r2tsn to be acked */ + uint32_t datain_datasn; + uint32_t acked_data_sn; /* next expected datain datasn */ + uint32_t ttt; + bool is_r2t_active; + + uint32_t tag; + + /** + * Record the lun id just in case the lun is invalid, + * which will happen when hot removing the lun. + */ + int lun_id; + + struct spdk_poller *mgmt_poller; + + TAILQ_ENTRY(spdk_iscsi_task) link; + + TAILQ_HEAD(subtask_list, spdk_iscsi_task) subtask_list; + TAILQ_ENTRY(spdk_iscsi_task) subtask_link; + bool is_queued; /* is queued in scsi layer for handling */ +}; + +static inline void +iscsi_task_put(struct spdk_iscsi_task *task) +{ + spdk_scsi_task_put(&task->scsi); +} + +static inline struct spdk_iscsi_pdu * +iscsi_task_get_pdu(struct spdk_iscsi_task *task) +{ + return task->pdu; +} + +static inline void +iscsi_task_set_pdu(struct spdk_iscsi_task *task, struct spdk_iscsi_pdu *pdu) +{ + task->pdu = pdu; +} + +static inline struct iscsi_bhs * +iscsi_task_get_bhs(struct spdk_iscsi_task *task) +{ + return &iscsi_task_get_pdu(task)->bhs; +} + +static inline void +iscsi_task_associate_pdu(struct spdk_iscsi_task *task, struct spdk_iscsi_pdu *pdu) +{ + iscsi_task_set_pdu(task, pdu); + pdu->ref++; +} + +static inline void +iscsi_task_disassociate_pdu(struct spdk_iscsi_task *task) +{ + if (iscsi_task_get_pdu(task)) { + iscsi_put_pdu(iscsi_task_get_pdu(task)); + iscsi_task_set_pdu(task, NULL); + } +} + +static inline int +iscsi_task_is_immediate(struct spdk_iscsi_task *task) +{ + struct iscsi_bhs_scsi_req *scsi_req; + + scsi_req = (struct iscsi_bhs_scsi_req *)iscsi_task_get_bhs(task); + return (scsi_req->immediate == 1); +} + +static inline int +iscsi_task_is_read(struct spdk_iscsi_task *task) +{ + struct iscsi_bhs_scsi_req *scsi_req; + + scsi_req = (struct iscsi_bhs_scsi_req *)iscsi_task_get_bhs(task); + return (scsi_req->read_bit == 1); +} + +struct spdk_iscsi_task *iscsi_task_get(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *parent, + spdk_scsi_task_cpl cpl_fn); + +static inline struct spdk_iscsi_task * +iscsi_task_from_scsi_task(struct spdk_scsi_task *task) +{ + return SPDK_CONTAINEROF(task, struct spdk_iscsi_task, scsi); +} + +static inline struct spdk_iscsi_task * +iscsi_task_get_primary(struct spdk_iscsi_task *task) +{ + if (task->parent) { + return task->parent; + } else { + return task; + } +} + +#endif /* SPDK_ISCSI_TASK_H */ diff --git a/src/spdk/lib/iscsi/tgt_node.c b/src/spdk/lib/iscsi/tgt_node.c new file mode 100644 index 000000000..0807a3384 --- /dev/null +++ b/src/spdk/lib/iscsi/tgt_node.c @@ -0,0 +1,1607 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include "spdk/conf.h" +#include "spdk/sock.h" +#include "spdk/scsi.h" + +#include "spdk_internal/log.h" + +#include "iscsi/iscsi.h" +#include "iscsi/conn.h" +#include "iscsi/tgt_node.h" +#include "iscsi/portal_grp.h" +#include "iscsi/init_grp.h" +#include "iscsi/task.h" + +#define MAX_TMPBUF 4096 +#define MAX_MASKBUF 128 + +static bool +iscsi_ipv6_netmask_allow_addr(const char *netmask, const char *addr) +{ + struct in6_addr in6_mask; + struct in6_addr in6_addr; + char mask[MAX_MASKBUF]; + const char *p; + size_t n; + int bits, bmask; + int i; + + if (netmask[0] != '[') { + return false; + } + p = strchr(netmask, ']'); + if (p == NULL) { + return false; + } + n = p - (netmask + 1); + if (n + 1 > sizeof mask) { + return false; + } + + memcpy(mask, netmask + 1, n); + mask[n] = '\0'; + p++; + + if (p[0] == '/') { + bits = (int) strtol(p + 1, NULL, 10); + if (bits <= 0 || bits > 128) { + return false; + } + } else { + bits = 128; + } + +#if 0 + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "input %s\n", addr); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "mask %s / %d\n", mask, bits); +#endif + + /* presentation to network order binary */ + if (inet_pton(AF_INET6, mask, &in6_mask) <= 0 + || inet_pton(AF_INET6, addr, &in6_addr) <= 0) { + return false; + } + + /* check 128bits */ + for (i = 0; i < (bits / 8); i++) { + if (in6_mask.s6_addr[i] != in6_addr.s6_addr[i]) { + return false; + } + } + if (bits % 8) { + bmask = (0xffU << (8 - (bits % 8))) & 0xffU; + if ((in6_mask.s6_addr[i] & bmask) != (in6_addr.s6_addr[i] & bmask)) { + return false; + } + } + + /* match */ + return true; +} + +static bool +iscsi_ipv4_netmask_allow_addr(const char *netmask, const char *addr) +{ + struct in_addr in4_mask; + struct in_addr in4_addr; + char mask[MAX_MASKBUF]; + const char *p; + uint32_t bmask; + size_t n; + int bits; + + p = strchr(netmask, '/'); + if (p == NULL) { + p = netmask + strlen(netmask); + } + n = p - netmask; + if (n + 1 > sizeof mask) { + return false; + } + + memcpy(mask, netmask, n); + mask[n] = '\0'; + + if (p[0] == '/') { + bits = (int) strtol(p + 1, NULL, 10); + if (bits <= 0 || bits > 32) { + return false; + } + } else { + bits = 32; + } + + /* presentation to network order binary */ + if (inet_pton(AF_INET, mask, &in4_mask) <= 0 + || inet_pton(AF_INET, addr, &in4_addr) <= 0) { + return false; + } + + /* check 32bits */ + bmask = (0xffffffffU << (32 - bits)) & 0xffffffffU; + if ((ntohl(in4_mask.s_addr) & bmask) != (ntohl(in4_addr.s_addr) & bmask)) { + return false; + } + + /* match */ + return true; +} + +static bool +iscsi_netmask_allow_addr(const char *netmask, const char *addr) +{ + if (netmask == NULL || addr == NULL) { + return false; + } + if (strcasecmp(netmask, "ANY") == 0) { + return true; + } + if (netmask[0] == '[') { + /* IPv6 */ + if (iscsi_ipv6_netmask_allow_addr(netmask, addr)) { + return true; + } + } else { + /* IPv4 */ + if (iscsi_ipv4_netmask_allow_addr(netmask, addr)) { + return true; + } + } + return false; +} + +static bool +iscsi_init_grp_allow_addr(struct spdk_iscsi_init_grp *igp, + const char *addr) +{ + struct spdk_iscsi_initiator_netmask *imask; + + TAILQ_FOREACH(imask, &igp->netmask_head, tailq) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "netmask=%s, addr=%s\n", + imask->mask, addr); + if (iscsi_netmask_allow_addr(imask->mask, addr)) { + return true; + } + } + return false; +} + +static int +iscsi_init_grp_allow_iscsi_name(struct spdk_iscsi_init_grp *igp, + const char *iqn, bool *result) +{ + struct spdk_iscsi_initiator_name *iname; + + TAILQ_FOREACH(iname, &igp->initiator_head, tailq) { + /* denied if iqn is matched */ + if ((iname->name[0] == '!') + && (strcasecmp(&iname->name[1], "ANY") == 0 + || strcasecmp(&iname->name[1], iqn) == 0)) { + *result = false; + return 0; + } + /* allowed if iqn is matched */ + if (strcasecmp(iname->name, "ANY") == 0 + || strcasecmp(iname->name, iqn) == 0) { + *result = true; + return 0; + } + } + return -1; +} + +static struct spdk_iscsi_pg_map * +iscsi_tgt_node_find_pg_map(struct spdk_iscsi_tgt_node *target, + struct spdk_iscsi_portal_grp *pg); + +bool +iscsi_tgt_node_access(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_tgt_node *target, const char *iqn, const char *addr) +{ + struct spdk_iscsi_portal_grp *pg; + struct spdk_iscsi_pg_map *pg_map; + struct spdk_iscsi_ig_map *ig_map; + int rc; + bool allowed = false; + + if (conn == NULL || target == NULL || iqn == NULL || addr == NULL) { + return false; + } + pg = conn->portal->group; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "pg=%d, iqn=%s, addr=%s\n", + pg->tag, iqn, addr); + pg_map = iscsi_tgt_node_find_pg_map(target, pg); + if (pg_map == NULL) { + return false; + } + TAILQ_FOREACH(ig_map, &pg_map->ig_map_head, tailq) { + rc = iscsi_init_grp_allow_iscsi_name(ig_map->ig, iqn, &allowed); + if (rc == 0) { + if (allowed == false) { + goto denied; + } else { + if (iscsi_init_grp_allow_addr(ig_map->ig, addr)) { + return true; + } + } + } else { + /* netmask is denied in this initiator group */ + } + } + +denied: + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "access denied from %s (%s) to %s (%s:%s,%d)\n", + iqn, addr, target->name, conn->portal_host, + conn->portal_port, conn->pg_tag); + return false; +} + +static bool +iscsi_tgt_node_allow_iscsi_name(struct spdk_iscsi_tgt_node *target, const char *iqn) +{ + struct spdk_iscsi_pg_map *pg_map; + struct spdk_iscsi_ig_map *ig_map; + int rc; + bool result = false; + + if (target == NULL || iqn == NULL) { + return false; + } + + TAILQ_FOREACH(pg_map, &target->pg_map_head, tailq) { + TAILQ_FOREACH(ig_map, &pg_map->ig_map_head, tailq) { + rc = iscsi_init_grp_allow_iscsi_name(ig_map->ig, iqn, &result); + if (rc == 0) { + return result; + } + } + } + + return false; +} + +int +iscsi_send_tgts(struct spdk_iscsi_conn *conn, const char *iiqn, + const char *iaddr, const char *tiqn, uint8_t *data, int alloc_len, + int data_len) +{ + char buf[MAX_TMPBUF]; + struct spdk_iscsi_portal_grp *pg; + struct spdk_iscsi_pg_map *pg_map; + struct spdk_iscsi_portal *p; + struct spdk_iscsi_tgt_node *target; + char *host; + int total; + int len; + int rc; + + if (conn == NULL) { + return 0; + } + + total = data_len; + if (alloc_len < 1) { + return 0; + } + if (total >= alloc_len) { + total = alloc_len; + data[total - 1] = '\0'; + return total; + } + + pthread_mutex_lock(&g_iscsi.mutex); + TAILQ_FOREACH(target, &g_iscsi.target_head, tailq) { + if (strcasecmp(tiqn, "ALL") != 0 + && strcasecmp(tiqn, target->name) != 0) { + continue; + } + rc = iscsi_tgt_node_allow_iscsi_name(target, iiqn); + if (rc == 0) { + continue; + } + + /* DO SENDTARGETS */ + len = snprintf((char *) data + total, alloc_len - total, + "TargetName=%s", target->name); + total += len + 1; + + /* write to data */ + TAILQ_FOREACH(pg_map, &target->pg_map_head, tailq) { + pg = pg_map->pg; + TAILQ_FOREACH(p, &pg->head, per_pg_tailq) { + if (alloc_len - total < 1) { + pthread_mutex_unlock(&g_iscsi.mutex); + /* TODO: long text responses support */ + SPDK_ERRLOG("SPDK doesn't support long text responses now, " + "you can use larger MaxRecvDataSegmentLength" + "value in initiator\n"); + return alloc_len; + } + host = p->host; + /* wildcard? */ + if (strcasecmp(host, "[::]") == 0 + || strcasecmp(host, "0.0.0.0") == 0) { + if (spdk_sock_is_ipv6(conn->sock)) { + snprintf(buf, sizeof buf, "[%s]", + conn->target_addr); + host = buf; + } else if (spdk_sock_is_ipv4(conn->sock)) { + snprintf(buf, sizeof buf, "%s", + conn->target_addr); + host = buf; + } else { + /* skip portal for the family */ + continue; + } + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "TargetAddress=%s:%s,%d\n", + host, p->port, pg->tag); + len = snprintf((char *) data + total, + alloc_len - total, + "TargetAddress=%s:%s,%d", + host, p->port, pg->tag); + total += len + 1; + } + } + } + pthread_mutex_unlock(&g_iscsi.mutex); + + return total; +} + +struct spdk_iscsi_tgt_node * +iscsi_find_tgt_node(const char *target_name) +{ + struct spdk_iscsi_tgt_node *target; + + if (target_name == NULL) { + return NULL; + } + TAILQ_FOREACH(target, &g_iscsi.target_head, tailq) { + if (strcasecmp(target_name, target->name) == 0) { + return target; + } + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "can't find target %s\n", target_name); + return NULL; +} + +static int +iscsi_tgt_node_register(struct spdk_iscsi_tgt_node *target) +{ + pthread_mutex_lock(&g_iscsi.mutex); + + if (iscsi_find_tgt_node(target->name) != NULL) { + pthread_mutex_unlock(&g_iscsi.mutex); + return -EEXIST; + } + + TAILQ_INSERT_TAIL(&g_iscsi.target_head, target, tailq); + + pthread_mutex_unlock(&g_iscsi.mutex); + return 0; +} + +static int +iscsi_tgt_node_unregister(struct spdk_iscsi_tgt_node *target) +{ + struct spdk_iscsi_tgt_node *t; + + TAILQ_FOREACH(t, &g_iscsi.target_head, tailq) { + if (t == target) { + TAILQ_REMOVE(&g_iscsi.target_head, t, tailq); + return 0; + } + } + + return -1; +} + +static struct spdk_iscsi_ig_map * +iscsi_pg_map_find_ig_map(struct spdk_iscsi_pg_map *pg_map, + struct spdk_iscsi_init_grp *ig) +{ + struct spdk_iscsi_ig_map *ig_map; + + TAILQ_FOREACH(ig_map, &pg_map->ig_map_head, tailq) { + if (ig_map->ig == ig) { + return ig_map; + } + } + + return NULL; +} + +static struct spdk_iscsi_ig_map * +iscsi_pg_map_add_ig_map(struct spdk_iscsi_pg_map *pg_map, + struct spdk_iscsi_init_grp *ig) +{ + struct spdk_iscsi_ig_map *ig_map; + + if (iscsi_pg_map_find_ig_map(pg_map, ig) != NULL) { + return NULL; + } + + ig_map = malloc(sizeof(*ig_map)); + if (ig_map == NULL) { + return NULL; + } + + ig_map->ig = ig; + ig->ref++; + pg_map->num_ig_maps++; + TAILQ_INSERT_TAIL(&pg_map->ig_map_head, ig_map, tailq); + + return ig_map; +} + +static void +_iscsi_pg_map_delete_ig_map(struct spdk_iscsi_pg_map *pg_map, + struct spdk_iscsi_ig_map *ig_map) +{ + TAILQ_REMOVE(&pg_map->ig_map_head, ig_map, tailq); + pg_map->num_ig_maps--; + ig_map->ig->ref--; + free(ig_map); +} + +static int +iscsi_pg_map_delete_ig_map(struct spdk_iscsi_pg_map *pg_map, + struct spdk_iscsi_init_grp *ig) +{ + struct spdk_iscsi_ig_map *ig_map; + + ig_map = iscsi_pg_map_find_ig_map(pg_map, ig); + if (ig_map == NULL) { + return -ENOENT; + } + + _iscsi_pg_map_delete_ig_map(pg_map, ig_map); + return 0; +} + +static void +iscsi_pg_map_delete_all_ig_maps(struct spdk_iscsi_pg_map *pg_map) +{ + struct spdk_iscsi_ig_map *ig_map, *tmp; + + TAILQ_FOREACH_SAFE(ig_map, &pg_map->ig_map_head, tailq, tmp) { + _iscsi_pg_map_delete_ig_map(pg_map, ig_map); + } +} + +static struct spdk_iscsi_pg_map * +iscsi_tgt_node_find_pg_map(struct spdk_iscsi_tgt_node *target, + struct spdk_iscsi_portal_grp *pg) +{ + struct spdk_iscsi_pg_map *pg_map; + + TAILQ_FOREACH(pg_map, &target->pg_map_head, tailq) { + if (pg_map->pg == pg) { + return pg_map; + } + } + + return NULL; +} + +static struct spdk_iscsi_pg_map * +iscsi_tgt_node_add_pg_map(struct spdk_iscsi_tgt_node *target, + struct spdk_iscsi_portal_grp *pg) +{ + struct spdk_iscsi_pg_map *pg_map; + char port_name[MAX_TMPBUF]; + int rc; + + if (iscsi_tgt_node_find_pg_map(target, pg) != NULL) { + return NULL; + } + + if (target->num_pg_maps >= SPDK_SCSI_DEV_MAX_PORTS) { + SPDK_ERRLOG("Number of PG maps is more than allowed (max=%d)\n", + SPDK_SCSI_DEV_MAX_PORTS); + return NULL; + } + + pg_map = malloc(sizeof(*pg_map)); + if (pg_map == NULL) { + return NULL; + } + + snprintf(port_name, sizeof(port_name), "%s,t,0x%4.4x", + spdk_scsi_dev_get_name(target->dev), pg->tag); + rc = spdk_scsi_dev_add_port(target->dev, pg->tag, port_name); + if (rc != 0) { + free(pg_map); + return NULL; + } + + TAILQ_INIT(&pg_map->ig_map_head); + pg_map->num_ig_maps = 0; + pg->ref++; + pg_map->pg = pg; + target->num_pg_maps++; + TAILQ_INSERT_TAIL(&target->pg_map_head, pg_map, tailq); + + return pg_map; +} + +static void +_iscsi_tgt_node_delete_pg_map(struct spdk_iscsi_tgt_node *target, + struct spdk_iscsi_pg_map *pg_map) +{ + TAILQ_REMOVE(&target->pg_map_head, pg_map, tailq); + target->num_pg_maps--; + pg_map->pg->ref--; + + spdk_scsi_dev_delete_port(target->dev, pg_map->pg->tag); + + free(pg_map); +} + +static int +iscsi_tgt_node_delete_pg_map(struct spdk_iscsi_tgt_node *target, + struct spdk_iscsi_portal_grp *pg) +{ + struct spdk_iscsi_pg_map *pg_map; + + pg_map = iscsi_tgt_node_find_pg_map(target, pg); + if (pg_map == NULL) { + return -ENOENT; + } + + if (pg_map->num_ig_maps > 0) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "delete %d ig_maps forcefully\n", + pg_map->num_ig_maps); + } + + iscsi_pg_map_delete_all_ig_maps(pg_map); + _iscsi_tgt_node_delete_pg_map(target, pg_map); + return 0; +} + +static void +iscsi_tgt_node_delete_ig_maps(struct spdk_iscsi_tgt_node *target, + struct spdk_iscsi_init_grp *ig) +{ + struct spdk_iscsi_pg_map *pg_map, *tmp; + + TAILQ_FOREACH_SAFE(pg_map, &target->pg_map_head, tailq, tmp) { + iscsi_pg_map_delete_ig_map(pg_map, ig); + if (pg_map->num_ig_maps == 0) { + _iscsi_tgt_node_delete_pg_map(target, pg_map); + } + } +} + +static void +iscsi_tgt_node_delete_all_pg_maps(struct spdk_iscsi_tgt_node *target) +{ + struct spdk_iscsi_pg_map *pg_map, *tmp; + + TAILQ_FOREACH_SAFE(pg_map, &target->pg_map_head, tailq, tmp) { + iscsi_pg_map_delete_all_ig_maps(pg_map); + _iscsi_tgt_node_delete_pg_map(target, pg_map); + } +} + +static void +_iscsi_tgt_node_destruct(void *cb_arg, int rc) +{ + struct spdk_iscsi_tgt_node *target = cb_arg; + iscsi_tgt_node_destruct_cb destruct_cb_fn = target->destruct_cb_fn; + void *destruct_cb_arg = target->destruct_cb_arg; + + if (rc != 0) { + if (destruct_cb_fn) { + destruct_cb_fn(destruct_cb_arg, rc); + } + return; + } + + pthread_mutex_lock(&g_iscsi.mutex); + iscsi_tgt_node_delete_all_pg_maps(target); + pthread_mutex_unlock(&g_iscsi.mutex); + + pthread_mutex_destroy(&target->mutex); + free(target); + + if (destruct_cb_fn) { + destruct_cb_fn(destruct_cb_arg, 0); + } +} + +static int +iscsi_tgt_node_check_active_conns(void *arg) +{ + struct spdk_iscsi_tgt_node *target = arg; + + if (iscsi_get_active_conns(target) != 0) { + return SPDK_POLLER_BUSY; + } + + spdk_poller_unregister(&target->destruct_poller); + + spdk_scsi_dev_destruct(target->dev, _iscsi_tgt_node_destruct, target); + + return SPDK_POLLER_BUSY; +} + +static void +iscsi_tgt_node_destruct(struct spdk_iscsi_tgt_node *target, + iscsi_tgt_node_destruct_cb cb_fn, void *cb_arg) +{ + if (target == NULL) { + if (cb_fn) { + cb_fn(cb_arg, -ENOENT); + } + return; + } + + if (target->destructed) { + SPDK_ERRLOG("Destructing %s is already started\n", target->name); + if (cb_fn) { + cb_fn(cb_arg, -EBUSY); + } + return; + } + + target->destructed = true; + target->destruct_cb_fn = cb_fn; + target->destruct_cb_arg = cb_arg; + + iscsi_conns_request_logout(target); + + if (iscsi_get_active_conns(target) != 0) { + target->destruct_poller = SPDK_POLLER_REGISTER(iscsi_tgt_node_check_active_conns, + target, 10); + } else { + spdk_scsi_dev_destruct(target->dev, _iscsi_tgt_node_destruct, target); + } + +} + +static int +iscsi_tgt_node_delete_pg_ig_map(struct spdk_iscsi_tgt_node *target, + int pg_tag, int ig_tag) +{ + struct spdk_iscsi_portal_grp *pg; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_pg_map *pg_map; + struct spdk_iscsi_ig_map *ig_map; + + pg = iscsi_portal_grp_find_by_tag(pg_tag); + if (pg == NULL) { + SPDK_ERRLOG("%s: PortalGroup%d not found\n", target->name, pg_tag); + return -ENOENT; + } + ig = iscsi_init_grp_find_by_tag(ig_tag); + if (ig == NULL) { + SPDK_ERRLOG("%s: InitiatorGroup%d not found\n", target->name, ig_tag); + return -ENOENT; + } + + pg_map = iscsi_tgt_node_find_pg_map(target, pg); + if (pg_map == NULL) { + SPDK_ERRLOG("%s: PortalGroup%d is not mapped\n", target->name, pg_tag); + return -ENOENT; + } + ig_map = iscsi_pg_map_find_ig_map(pg_map, ig); + if (ig_map == NULL) { + SPDK_ERRLOG("%s: InitiatorGroup%d is not mapped\n", target->name, pg_tag); + return -ENOENT; + } + + _iscsi_pg_map_delete_ig_map(pg_map, ig_map); + if (pg_map->num_ig_maps == 0) { + _iscsi_tgt_node_delete_pg_map(target, pg_map); + } + + return 0; +} + +static int +iscsi_tgt_node_add_pg_ig_map(struct spdk_iscsi_tgt_node *target, + int pg_tag, int ig_tag) +{ + struct spdk_iscsi_portal_grp *pg; + struct spdk_iscsi_pg_map *pg_map; + struct spdk_iscsi_init_grp *ig; + struct spdk_iscsi_ig_map *ig_map; + bool new_pg_map = false; + + pg = iscsi_portal_grp_find_by_tag(pg_tag); + if (pg == NULL) { + SPDK_ERRLOG("%s: PortalGroup%d not found\n", target->name, pg_tag); + return -ENOENT; + } + ig = iscsi_init_grp_find_by_tag(ig_tag); + if (ig == NULL) { + SPDK_ERRLOG("%s: InitiatorGroup%d not found\n", target->name, ig_tag); + return -ENOENT; + } + + /* get existing pg_map or create new pg_map and add it to target */ + pg_map = iscsi_tgt_node_find_pg_map(target, pg); + if (pg_map == NULL) { + pg_map = iscsi_tgt_node_add_pg_map(target, pg); + if (pg_map == NULL) { + goto failed; + } + new_pg_map = true; + } + + /* create new ig_map and add it to pg_map */ + ig_map = iscsi_pg_map_add_ig_map(pg_map, ig); + if (ig_map == NULL) { + goto failed; + } + + return 0; + +failed: + if (new_pg_map) { + _iscsi_tgt_node_delete_pg_map(target, pg_map); + } + + return -1; +} + +int +iscsi_target_node_add_pg_ig_maps(struct spdk_iscsi_tgt_node *target, + int *pg_tag_list, int *ig_tag_list, uint16_t num_maps) +{ + uint16_t i; + int rc; + + pthread_mutex_lock(&g_iscsi.mutex); + for (i = 0; i < num_maps; i++) { + rc = iscsi_tgt_node_add_pg_ig_map(target, pg_tag_list[i], + ig_tag_list[i]); + if (rc != 0) { + SPDK_ERRLOG("could not add map to target\n"); + goto invalid; + } + } + pthread_mutex_unlock(&g_iscsi.mutex); + return 0; + +invalid: + for (; i > 0; --i) { + iscsi_tgt_node_delete_pg_ig_map(target, pg_tag_list[i - 1], + ig_tag_list[i - 1]); + } + pthread_mutex_unlock(&g_iscsi.mutex); + return -1; +} + +int +iscsi_target_node_remove_pg_ig_maps(struct spdk_iscsi_tgt_node *target, + int *pg_tag_list, int *ig_tag_list, uint16_t num_maps) +{ + uint16_t i; + int rc; + + pthread_mutex_lock(&g_iscsi.mutex); + for (i = 0; i < num_maps; i++) { + rc = iscsi_tgt_node_delete_pg_ig_map(target, pg_tag_list[i], + ig_tag_list[i]); + if (rc != 0) { + SPDK_ERRLOG("could not delete map from target\n"); + goto invalid; + } + } + pthread_mutex_unlock(&g_iscsi.mutex); + return 0; + +invalid: + for (; i > 0; --i) { + rc = iscsi_tgt_node_add_pg_ig_map(target, pg_tag_list[i - 1], + ig_tag_list[i - 1]); + if (rc != 0) { + iscsi_tgt_node_delete_all_pg_maps(target); + break; + } + } + pthread_mutex_unlock(&g_iscsi.mutex); + return -1; +} + +static int +check_iscsi_name(const char *name) +{ + const unsigned char *up = (const unsigned char *) name; + size_t n; + + /* valid iSCSI name no larger than 223 bytes */ + if (strlen(name) > MAX_TARGET_NAME) { + return -1; + } + + /* valid iSCSI name? */ + for (n = 0; up[n] != 0; n++) { + if (up[n] > 0x00U && up[n] <= 0x2cU) { + return -1; + } + if (up[n] == 0x2fU) { + return -1; + } + if (up[n] >= 0x3bU && up[n] <= 0x40U) { + return -1; + } + if (up[n] >= 0x5bU && up[n] <= 0x60U) { + return -1; + } + if (up[n] >= 0x7bU && up[n] <= 0x7fU) { + return -1; + } + if (isspace(up[n])) { + return -1; + } + } + /* valid format? */ + if (strncasecmp(name, "iqn.", 4) == 0) { + /* iqn.YYYY-MM.reversed.domain.name */ + if (!isdigit(up[4]) || !isdigit(up[5]) || !isdigit(up[6]) + || !isdigit(up[7]) || up[8] != '-' || !isdigit(up[9]) + || !isdigit(up[10]) || up[11] != '.') { + SPDK_ERRLOG("invalid iqn format. " + "expect \"iqn.YYYY-MM.reversed.domain.name\"\n"); + return -1; + } + } else if (strncasecmp(name, "eui.", 4) == 0) { + /* EUI-64 -> 16bytes */ + /* XXX */ + } else if (strncasecmp(name, "naa.", 4) == 0) { + /* 64bit -> 16bytes, 128bit -> 32bytes */ + /* XXX */ + } + /* OK */ + return 0; +} + +bool +iscsi_check_chap_params(bool disable, bool require, bool mutual, int group) +{ + if (group < 0) { + SPDK_ERRLOG("Invalid auth group ID (%d)\n", group); + return false; + } + if ((!disable && !require && !mutual) || /* Auto */ + (disable && !require && !mutual) || /* None */ + (!disable && require && !mutual) || /* CHAP */ + (!disable && require && mutual)) { /* CHAP Mutual */ + return true; + } + SPDK_ERRLOG("Invalid combination of CHAP params (d=%d,r=%d,m=%d)\n", + disable, require, mutual); + return false; +} + +struct spdk_iscsi_tgt_node *iscsi_tgt_node_construct(int target_index, + const char *name, const char *alias, + int *pg_tag_list, int *ig_tag_list, uint16_t num_maps, + const char *bdev_name_list[], int *lun_id_list, int num_luns, + int queue_depth, + bool disable_chap, bool require_chap, bool mutual_chap, int chap_group, + bool header_digest, bool data_digest) +{ + char fullname[MAX_TMPBUF]; + struct spdk_iscsi_tgt_node *target; + int rc; + + if (!iscsi_check_chap_params(disable_chap, require_chap, + mutual_chap, chap_group)) { + return NULL; + } + + if (num_maps == 0) { + SPDK_ERRLOG("num_maps = 0\n"); + return NULL; + } + + if (name == NULL) { + SPDK_ERRLOG("TargetName not found\n"); + return NULL; + } + + if (strncasecmp(name, "iqn.", 4) != 0 + && strncasecmp(name, "eui.", 4) != 0 + && strncasecmp(name, "naa.", 4) != 0) { + snprintf(fullname, sizeof(fullname), "%s:%s", g_iscsi.nodebase, name); + } else { + snprintf(fullname, sizeof(fullname), "%s", name); + } + + if (check_iscsi_name(fullname) != 0) { + SPDK_ERRLOG("TargetName %s contains an invalid character or format.\n", + name); + return NULL; + } + + target = calloc(1, sizeof(*target)); + if (!target) { + SPDK_ERRLOG("could not allocate target\n"); + return NULL; + } + + rc = pthread_mutex_init(&target->mutex, NULL); + if (rc != 0) { + SPDK_ERRLOG("tgt_node%d: mutex_init() failed\n", target->num); + iscsi_tgt_node_destruct(target, NULL, NULL); + return NULL; + } + + target->num = target_index; + + memcpy(target->name, fullname, strlen(fullname)); + + if (alias != NULL) { + if (strlen(alias) > MAX_TARGET_NAME) { + iscsi_tgt_node_destruct(target, NULL, NULL); + return NULL; + } + memcpy(target->alias, alias, strlen(alias)); + } + + target->dev = spdk_scsi_dev_construct(fullname, bdev_name_list, lun_id_list, num_luns, + SPDK_SPC_PROTOCOL_IDENTIFIER_ISCSI, NULL, NULL); + if (!target->dev) { + SPDK_ERRLOG("Could not construct SCSI device\n"); + iscsi_tgt_node_destruct(target, NULL, NULL); + return NULL; + } + + TAILQ_INIT(&target->pg_map_head); + rc = iscsi_target_node_add_pg_ig_maps(target, pg_tag_list, + ig_tag_list, num_maps); + if (rc != 0) { + SPDK_ERRLOG("could not add map to target\n"); + iscsi_tgt_node_destruct(target, NULL, NULL); + return NULL; + } + + target->disable_chap = disable_chap; + target->require_chap = require_chap; + target->mutual_chap = mutual_chap; + target->chap_group = chap_group; + target->header_digest = header_digest; + target->data_digest = data_digest; + + if (queue_depth > 0 && ((uint32_t)queue_depth <= g_iscsi.MaxQueueDepth)) { + target->queue_depth = queue_depth; + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "QueueDepth %d is invalid and %d is used instead.\n", + queue_depth, g_iscsi.MaxQueueDepth); + target->queue_depth = g_iscsi.MaxQueueDepth; + } + + rc = iscsi_tgt_node_register(target); + if (rc != 0) { + SPDK_ERRLOG("register target is failed\n"); + iscsi_tgt_node_destruct(target, NULL, NULL); + return NULL; + } + + return target; +} + +static int +iscsi_parse_tgt_node(struct spdk_conf_section *sp) +{ + char buf[MAX_TMPBUF]; + struct spdk_iscsi_tgt_node *target; + int pg_tag_list[MAX_TARGET_MAP], ig_tag_list[MAX_TARGET_MAP]; + int num_target_maps; + const char *alias, *pg_tag, *ig_tag; + const char *ag_tag; + const char *val, *name; + int target_num, chap_group, pg_tag_i, ig_tag_i; + bool header_digest, data_digest; + bool disable_chap, require_chap, mutual_chap; + int i; + int lun_id_list[SPDK_SCSI_DEV_MAX_LUN]; + const char *bdev_name_list[SPDK_SCSI_DEV_MAX_LUN]; + int num_luns, queue_depth; + + target_num = spdk_conf_section_get_num(sp); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "add unit %d\n", target_num); + + data_digest = false; + header_digest = false; + + name = spdk_conf_section_get_val(sp, "TargetName"); + + if (name == NULL) { + SPDK_ERRLOG("tgt_node%d: TargetName not found\n", target_num); + return -1; + } + + alias = spdk_conf_section_get_val(sp, "TargetAlias"); + + /* Setup initiator and portal group mapping */ + val = spdk_conf_section_get_val(sp, "Mapping"); + if (val == NULL) { + /* no map */ + SPDK_ERRLOG("tgt_node%d: no Mapping\n", target_num); + return -1; + } + + for (i = 0; i < MAX_TARGET_MAP; i++) { + val = spdk_conf_section_get_nmval(sp, "Mapping", i, 0); + if (val == NULL) { + break; + } + pg_tag = spdk_conf_section_get_nmval(sp, "Mapping", i, 0); + ig_tag = spdk_conf_section_get_nmval(sp, "Mapping", i, 1); + if (pg_tag == NULL || ig_tag == NULL) { + SPDK_ERRLOG("tgt_node%d: mapping error\n", target_num); + return -1; + } + if (strncasecmp(pg_tag, "PortalGroup", + strlen("PortalGroup")) != 0 + || sscanf(pg_tag, "%*[^0-9]%d", &pg_tag_i) != 1) { + SPDK_ERRLOG("tgt_node%d: mapping portal error\n", target_num); + return -1; + } + if (strncasecmp(ig_tag, "InitiatorGroup", + strlen("InitiatorGroup")) != 0 + || sscanf(ig_tag, "%*[^0-9]%d", &ig_tag_i) != 1) { + SPDK_ERRLOG("tgt_node%d: mapping initiator error\n", target_num); + return -1; + } + if (pg_tag_i < 1 || ig_tag_i < 1) { + SPDK_ERRLOG("tgt_node%d: invalid group tag\n", target_num); + return -1; + } + pg_tag_list[i] = pg_tag_i; + ig_tag_list[i] = ig_tag_i; + } + + num_target_maps = i; + + /* Setup AuthMethod */ + val = spdk_conf_section_get_val(sp, "AuthMethod"); + disable_chap = false; + require_chap = false; + mutual_chap = false; + if (val != NULL) { + for (i = 0; ; i++) { + val = spdk_conf_section_get_nmval(sp, "AuthMethod", 0, i); + if (val == NULL) { + break; + } + if (strcasecmp(val, "CHAP") == 0) { + require_chap = true; + } else if (strcasecmp(val, "Mutual") == 0) { + mutual_chap = true; + } else if (strcasecmp(val, "Auto") == 0) { + disable_chap = false; + require_chap = false; + mutual_chap = false; + } else if (strcasecmp(val, "None") == 0) { + disable_chap = true; + require_chap = false; + mutual_chap = false; + } else { + SPDK_ERRLOG("tgt_node%d: unknown auth\n", target_num); + return -1; + } + } + if (mutual_chap && !require_chap) { + SPDK_ERRLOG("tgt_node%d: Mutual but not CHAP\n", target_num); + return -1; + } + } + if (disable_chap) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "AuthMethod None\n"); + } else if (!require_chap) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "AuthMethod Auto\n"); + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "AuthMethod CHAP %s\n", + mutual_chap ? "Mutual" : ""); + } + + val = spdk_conf_section_get_val(sp, "AuthGroup"); + if (val == NULL) { + chap_group = 0; + } else { + ag_tag = val; + if (strcasecmp(ag_tag, "None") == 0) { + chap_group = 0; + } else { + if (strncasecmp(ag_tag, "AuthGroup", + strlen("AuthGroup")) != 0 + || sscanf(ag_tag, "%*[^0-9]%d", &chap_group) != 1) { + SPDK_ERRLOG("tgt_node%d: auth group error\n", target_num); + return -1; + } + if (chap_group == 0) { + SPDK_ERRLOG("tgt_node%d: invalid auth group 0\n", target_num); + return -1; + } + } + } + if (chap_group == 0) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "AuthGroup None\n"); + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "AuthGroup AuthGroup%d\n", chap_group); + } + + val = spdk_conf_section_get_val(sp, "UseDigest"); + if (val != NULL) { + for (i = 0; ; i++) { + val = spdk_conf_section_get_nmval(sp, "UseDigest", 0, i); + if (val == NULL) { + break; + } + if (strcasecmp(val, "Header") == 0) { + header_digest = true; + } else if (strcasecmp(val, "Data") == 0) { + data_digest = true; + } else if (strcasecmp(val, "Auto") == 0) { + header_digest = false; + data_digest = false; + } else { + SPDK_ERRLOG("tgt_node%d: unknown digest\n", target_num); + return -1; + } + } + } + if (!header_digest && !data_digest) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "UseDigest Auto\n"); + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "UseDigest %s %s\n", + header_digest ? "Header" : "", + data_digest ? "Data" : ""); + } + + val = spdk_conf_section_get_val(sp, "QueueDepth"); + if (val == NULL) { + queue_depth = g_iscsi.MaxQueueDepth; + } else { + queue_depth = (int) strtol(val, NULL, 10); + } + + num_luns = 0; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + snprintf(buf, sizeof(buf), "LUN%d", i); + val = spdk_conf_section_get_val(sp, buf); + if (val == NULL) { + continue; + } + + bdev_name_list[num_luns] = val; + lun_id_list[num_luns] = i; + num_luns++; + } + + if (num_luns == 0) { + SPDK_ERRLOG("tgt_node%d: No LUN specified for target %s.\n", target_num, name); + return -1; + } + + target = iscsi_tgt_node_construct(target_num, name, alias, + pg_tag_list, ig_tag_list, num_target_maps, + bdev_name_list, lun_id_list, num_luns, queue_depth, + disable_chap, require_chap, mutual_chap, chap_group, + header_digest, data_digest); + + if (target == NULL) { + SPDK_ERRLOG("tgt_node%d: add_iscsi_target_node error\n", target_num); + return -1; + } + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + struct spdk_scsi_lun *lun = spdk_scsi_dev_get_lun(target->dev, i); + + if (lun) { + SPDK_INFOLOG(SPDK_LOG_ISCSI, "device %d: LUN%d %s\n", + spdk_scsi_dev_get_id(target->dev), + spdk_scsi_lun_get_id(lun), + spdk_scsi_lun_get_bdev_name(lun)); + } + } + + return 0; +} + +int iscsi_parse_tgt_nodes(void) +{ + struct spdk_conf_section *sp; + int rc; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "iscsi_parse_tgt_nodes\n"); + + sp = spdk_conf_first_section(NULL); + while (sp != NULL) { + if (spdk_conf_section_match_prefix(sp, "TargetNode")) { + int tag = spdk_conf_section_get_num(sp); + + if (tag > SPDK_TN_TAG_MAX) { + SPDK_ERRLOG("tag %d is invalid\n", tag); + return -1; + } + rc = iscsi_parse_tgt_node(sp); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_parse_tgt_node() failed\n"); + return -1; + } + } + sp = spdk_conf_next_section(sp); + } + return 0; +} + +void +iscsi_shutdown_tgt_nodes(void) +{ + struct spdk_iscsi_tgt_node *target; + + pthread_mutex_lock(&g_iscsi.mutex); + while (!TAILQ_EMPTY(&g_iscsi.target_head)) { + target = TAILQ_FIRST(&g_iscsi.target_head); + TAILQ_REMOVE(&g_iscsi.target_head, target, tailq); + + pthread_mutex_unlock(&g_iscsi.mutex); + + iscsi_tgt_node_destruct(target, NULL, NULL); + + pthread_mutex_lock(&g_iscsi.mutex); + } + pthread_mutex_unlock(&g_iscsi.mutex); +} + +void +iscsi_shutdown_tgt_node_by_name(const char *target_name, + iscsi_tgt_node_destruct_cb cb_fn, void *cb_arg) +{ + struct spdk_iscsi_tgt_node *target; + + pthread_mutex_lock(&g_iscsi.mutex); + target = iscsi_find_tgt_node(target_name); + if (target != NULL) { + iscsi_tgt_node_unregister(target); + pthread_mutex_unlock(&g_iscsi.mutex); + + iscsi_tgt_node_destruct(target, cb_fn, cb_arg); + + return; + } + pthread_mutex_unlock(&g_iscsi.mutex); + + if (cb_fn) { + cb_fn(cb_arg, -ENOENT); + } +} + +bool +iscsi_tgt_node_is_destructed(struct spdk_iscsi_tgt_node *target) +{ + return target->destructed; +} + +int +iscsi_tgt_node_cleanup_luns(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_tgt_node *target) +{ + int i; + struct spdk_iscsi_task *task; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + struct spdk_scsi_lun *lun = spdk_scsi_dev_get_lun(target->dev, i); + + if (!lun) { + continue; + } + + /* we create a fake management task per LUN to cleanup */ + task = iscsi_task_get(conn, NULL, iscsi_task_mgmt_cpl); + if (!task) { + SPDK_ERRLOG("Unable to acquire task\n"); + return -1; + } + + task->scsi.target_port = conn->target_port; + task->scsi.initiator_port = conn->initiator_port; + task->scsi.lun = lun; + + iscsi_op_abort_task_set(task, SPDK_SCSI_TASK_FUNC_LUN_RESET); + } + + return 0; +} + +void iscsi_tgt_node_delete_map(struct spdk_iscsi_portal_grp *portal_group, + struct spdk_iscsi_init_grp *initiator_group) +{ + struct spdk_iscsi_tgt_node *target; + + pthread_mutex_lock(&g_iscsi.mutex); + TAILQ_FOREACH(target, &g_iscsi.target_head, tailq) { + if (portal_group) { + iscsi_tgt_node_delete_pg_map(target, portal_group); + } + if (initiator_group) { + iscsi_tgt_node_delete_ig_maps(target, initiator_group); + } + } + pthread_mutex_unlock(&g_iscsi.mutex); +} + +int +iscsi_tgt_node_add_lun(struct spdk_iscsi_tgt_node *target, + const char *bdev_name, int lun_id) +{ + struct spdk_scsi_dev *dev; + int rc; + + if (target->num_active_conns > 0) { + SPDK_ERRLOG("Target has active connections (count=%d)\n", + target->num_active_conns); + return -1; + } + + if (lun_id < -1 || lun_id >= SPDK_SCSI_DEV_MAX_LUN) { + SPDK_ERRLOG("Specified LUN ID (%d) is invalid\n", lun_id); + return -1; + } + + dev = target->dev; + if (dev == NULL) { + SPDK_ERRLOG("SCSI device is not found\n"); + return -1; + } + + rc = spdk_scsi_dev_add_lun(dev, bdev_name, lun_id, NULL, NULL); + if (rc != 0) { + SPDK_ERRLOG("spdk_scsi_dev_add_lun failed\n"); + return -1; + } + + return 0; +} + +int +iscsi_tgt_node_set_chap_params(struct spdk_iscsi_tgt_node *target, + bool disable_chap, bool require_chap, + bool mutual_chap, int32_t chap_group) +{ + if (!iscsi_check_chap_params(disable_chap, require_chap, + mutual_chap, chap_group)) { + return -EINVAL; + } + + pthread_mutex_lock(&target->mutex); + target->disable_chap = disable_chap; + target->require_chap = require_chap; + target->mutual_chap = mutual_chap; + target->chap_group = chap_group; + pthread_mutex_unlock(&target->mutex); + + return 0; +} + +static const char *target_nodes_section = \ + "\n" + "# Users should change the TargetNode section(s) below to match the\n" + "# desired iSCSI target node configuration.\n" + "# TargetName, Mapping, LUN0 are minimum required\n"; + +#define TARGET_NODE_TMPL \ +"[TargetNode%d]\n" \ +" Comment \"Target%d\"\n" \ +" TargetName %s\n" \ +" TargetAlias \"%s\"\n" + +#define TARGET_NODE_PGIG_MAPPING_TMPL \ +" Mapping PortalGroup%d InitiatorGroup%d\n" + +#define TARGET_NODE_AUTH_TMPL \ +" AuthMethod %s\n" \ +" AuthGroup %s\n" \ +" UseDigest %s\n" + +#define TARGET_NODE_QD_TMPL \ +" QueueDepth %d\n\n" + +#define TARGET_NODE_LUN_TMPL \ +" LUN%d %s\n" + +void +iscsi_tgt_nodes_config_text(FILE *fp) +{ + int l = 0; + struct spdk_scsi_dev *dev = NULL; + struct spdk_iscsi_tgt_node *target = NULL; + struct spdk_iscsi_pg_map *pg_map; + struct spdk_iscsi_ig_map *ig_map; + + /* Create target nodes section */ + fprintf(fp, "%s", target_nodes_section); + + TAILQ_FOREACH(target, &g_iscsi.target_head, tailq) { + int idx; + const char *authmethod = "None"; + char authgroup[32] = "None"; + const char *usedigest = "Auto"; + + dev = target->dev; + if (NULL == dev) { continue; } + + idx = target->num; + fprintf(fp, TARGET_NODE_TMPL, idx, idx, target->name, spdk_scsi_dev_get_name(dev)); + + TAILQ_FOREACH(pg_map, &target->pg_map_head, tailq) { + TAILQ_FOREACH(ig_map, &pg_map->ig_map_head, tailq) { + fprintf(fp, TARGET_NODE_PGIG_MAPPING_TMPL, + pg_map->pg->tag, + ig_map->ig->tag); + } + } + + if (target->disable_chap) { + authmethod = "None"; + } else if (!target->require_chap) { + authmethod = "Auto"; + } else if (target->mutual_chap) { + authmethod = "CHAP Mutual"; + } else { + authmethod = "CHAP"; + } + + if (target->chap_group > 0) { + snprintf(authgroup, sizeof(authgroup), "AuthGroup%d", target->chap_group); + } + + if (target->header_digest) { + usedigest = "Header"; + } else if (target->data_digest) { + usedigest = "Data"; + } + + fprintf(fp, TARGET_NODE_AUTH_TMPL, + authmethod, authgroup, usedigest); + + for (l = 0; l < SPDK_SCSI_DEV_MAX_LUN; l++) { + struct spdk_scsi_lun *lun = spdk_scsi_dev_get_lun(dev, l); + + if (!lun) { + continue; + } + + fprintf(fp, TARGET_NODE_LUN_TMPL, + spdk_scsi_lun_get_id(lun), + spdk_scsi_lun_get_bdev_name(lun)); + } + + fprintf(fp, TARGET_NODE_QD_TMPL, + target->queue_depth); + } +} + +static void +iscsi_tgt_node_info_json(struct spdk_iscsi_tgt_node *target, + struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_pg_map *pg_map; + struct spdk_iscsi_ig_map *ig_map; + int i; + + spdk_json_write_object_begin(w); + + spdk_json_write_named_string(w, "name", target->name); + + if (target->alias[0] != '\0') { + spdk_json_write_named_string(w, "alias_name", target->alias); + } + + spdk_json_write_named_array_begin(w, "pg_ig_maps"); + TAILQ_FOREACH(pg_map, &target->pg_map_head, tailq) { + TAILQ_FOREACH(ig_map, &pg_map->ig_map_head, tailq) { + spdk_json_write_object_begin(w); + spdk_json_write_named_int32(w, "pg_tag", pg_map->pg->tag); + spdk_json_write_named_int32(w, "ig_tag", ig_map->ig->tag); + spdk_json_write_object_end(w); + } + } + spdk_json_write_array_end(w); + + spdk_json_write_named_array_begin(w, "luns"); + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + struct spdk_scsi_lun *lun = spdk_scsi_dev_get_lun(target->dev, i); + + if (lun) { + spdk_json_write_object_begin(w); + spdk_json_write_named_string(w, "bdev_name", spdk_scsi_lun_get_bdev_name(lun)); + spdk_json_write_named_int32(w, "lun_id", spdk_scsi_lun_get_id(lun)); + spdk_json_write_object_end(w); + } + } + spdk_json_write_array_end(w); + + spdk_json_write_named_int32(w, "queue_depth", target->queue_depth); + + spdk_json_write_named_bool(w, "disable_chap", target->disable_chap); + spdk_json_write_named_bool(w, "require_chap", target->require_chap); + spdk_json_write_named_bool(w, "mutual_chap", target->mutual_chap); + spdk_json_write_named_int32(w, "chap_group", target->chap_group); + + spdk_json_write_named_bool(w, "header_digest", target->header_digest); + spdk_json_write_named_bool(w, "data_digest", target->data_digest); + + spdk_json_write_object_end(w); +} + +static void +iscsi_tgt_node_config_json(struct spdk_iscsi_tgt_node *target, + struct spdk_json_write_ctx *w) +{ + spdk_json_write_object_begin(w); + + spdk_json_write_named_string(w, "method", "iscsi_create_target_node"); + + spdk_json_write_name(w, "params"); + iscsi_tgt_node_info_json(target, w); + + spdk_json_write_object_end(w); +} + +void +iscsi_tgt_nodes_info_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_tgt_node *target; + + TAILQ_FOREACH(target, &g_iscsi.target_head, tailq) { + iscsi_tgt_node_info_json(target, w); + } +} + +void +iscsi_tgt_nodes_config_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_tgt_node *target; + + TAILQ_FOREACH(target, &g_iscsi.target_head, tailq) { + iscsi_tgt_node_config_json(target, w); + } +} diff --git a/src/spdk/lib/iscsi/tgt_node.h b/src/spdk/lib/iscsi/tgt_node.h new file mode 100644 index 000000000..2787fac91 --- /dev/null +++ b/src/spdk/lib/iscsi/tgt_node.h @@ -0,0 +1,147 @@ +/*- + * BSD LICENSE + * + * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>. + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SPDK_ISCSI_TGT_NODE_H_ +#define SPDK_ISCSI_TGT_NODE_H_ + +#include "spdk/stdinc.h" + +#include "iscsi/iscsi.h" + +struct spdk_iscsi_conn; +struct spdk_iscsi_init_grp; +struct spdk_iscsi_portal_grp; +struct spdk_iscsi_portal; +struct spdk_json_write_ctx; + +#define MAX_TARGET_MAP 256 +#define SPDK_TN_TAG_MAX 0x0000ffff + +typedef void (*iscsi_tgt_node_destruct_cb)(void *cb_arg, int rc); + +struct spdk_iscsi_ig_map { + struct spdk_iscsi_init_grp *ig; + TAILQ_ENTRY(spdk_iscsi_ig_map) tailq; +}; + +struct spdk_iscsi_pg_map { + struct spdk_iscsi_portal_grp *pg; + int num_ig_maps; + TAILQ_HEAD(, spdk_iscsi_ig_map) ig_map_head; + TAILQ_ENTRY(spdk_iscsi_pg_map) tailq ; +}; + +struct spdk_iscsi_tgt_node { + int num; + char name[MAX_TARGET_NAME + 1]; + char alias[MAX_TARGET_NAME + 1]; + + pthread_mutex_t mutex; + + bool disable_chap; + bool require_chap; + bool mutual_chap; + int chap_group; + bool header_digest; + bool data_digest; + int queue_depth; + + struct spdk_scsi_dev *dev; + /** + * Counts number of active iSCSI connections associated with this + * target node. + */ + uint32_t num_active_conns; + struct spdk_iscsi_poll_group *pg; + + int num_pg_maps; + TAILQ_HEAD(, spdk_iscsi_pg_map) pg_map_head; + TAILQ_ENTRY(spdk_iscsi_tgt_node) tailq; + + bool destructed; + struct spdk_poller *destruct_poller; + iscsi_tgt_node_destruct_cb destruct_cb_fn; + void *destruct_cb_arg; +}; + +int iscsi_parse_tgt_nodes(void); + +void iscsi_shutdown_tgt_nodes(void); +void iscsi_shutdown_tgt_node_by_name(const char *target_name, + iscsi_tgt_node_destruct_cb cb_fn, void *cb_arg); +bool iscsi_tgt_node_is_destructed(struct spdk_iscsi_tgt_node *target); +int iscsi_send_tgts(struct spdk_iscsi_conn *conn, const char *iiqn, + const char *iaddr, const char *tiqn, uint8_t *data, int alloc_len, + int data_len); + +/* + * bdev_name_list and lun_id_list are equal sized arrays of size num_luns. + * bdev_name_list refers to the names of the bdevs that will be used for the LUNs on the + * new target node. + * lun_id_list refers to the LUN IDs that will be used for the LUNs on the target node. + */ +struct spdk_iscsi_tgt_node *iscsi_tgt_node_construct(int target_index, + const char *name, const char *alias, + int *pg_tag_list, int *ig_tag_list, uint16_t num_maps, + const char *bdev_name_list[], int *lun_id_list, int num_luns, + int queue_depth, + bool disable_chap, bool require_chap, bool mutual_chap, int chap_group, + bool header_digest, bool data_digest); + +bool iscsi_check_chap_params(bool disable, bool require, bool mutual, int group); + +int iscsi_target_node_add_pg_ig_maps(struct spdk_iscsi_tgt_node *target, + int *pg_tag_list, int *ig_tag_list, + uint16_t num_maps); +int iscsi_target_node_remove_pg_ig_maps(struct spdk_iscsi_tgt_node *target, + int *pg_tag_list, int *ig_tag_list, + uint16_t num_maps); + +bool iscsi_tgt_node_access(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_tgt_node *target, const char *iqn, + const char *addr); +struct spdk_iscsi_tgt_node *iscsi_find_tgt_node(const char *target_name); +int iscsi_tgt_node_cleanup_luns(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_tgt_node *target); +void iscsi_tgt_node_delete_map(struct spdk_iscsi_portal_grp *portal_group, + struct spdk_iscsi_init_grp *initiator_group); +int iscsi_tgt_node_add_lun(struct spdk_iscsi_tgt_node *target, + const char *bdev_name, int lun_id); +int iscsi_tgt_node_set_chap_params(struct spdk_iscsi_tgt_node *target, + bool disable_chap, bool require_chap, + bool mutual_chap, int32_t chap_group); +void iscsi_tgt_nodes_config_text(FILE *fp); +void iscsi_tgt_nodes_info_json(struct spdk_json_write_ctx *w); +void iscsi_tgt_nodes_config_json(struct spdk_json_write_ctx *w); +#endif /* SPDK_ISCSI_TGT_NODE_H_ */ |