diff options
Diffstat (limited to 'src/spdk/lib/iscsi')
-rw-r--r-- | src/spdk/lib/iscsi/Makefile | 45 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/acceptor.c | 91 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/acceptor.h | 43 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/conn.c | 1470 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/conn.h | 193 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/init_grp.c | 786 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/init_grp.h | 79 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/iscsi.c | 4583 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/iscsi.h | 467 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/iscsi_rpc.c | 1542 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/iscsi_subsystem.c | 1523 | ||||
-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 | 1182 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/param.h | 84 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/portal_grp.c | 707 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/portal_grp.h | 83 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/task.c | 88 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/task.h | 187 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/tgt_node.c | 1538 | ||||
-rw-r--r-- | src/spdk/lib/iscsi/tgt_node.h | 146 |
21 files changed, 14964 insertions, 0 deletions
diff --git a/src/spdk/lib/iscsi/Makefile b/src/spdk/lib/iscsi/Makefile new file mode 100644 index 00000000..624bbf95 --- /dev/null +++ b/src/spdk/lib/iscsi/Makefile @@ -0,0 +1,45 @@ +# +# 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 + +CFLAGS += -I$(SPDK_ROOT_DIR)/lib +C_SRCS = acceptor.c 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 + +include $(SPDK_ROOT_DIR)/mk/spdk.lib.mk diff --git a/src/spdk/lib/iscsi/acceptor.c b/src/spdk/lib/iscsi/acceptor.c new file mode 100644 index 00000000..9b13de30 --- /dev/null +++ b/src/spdk/lib/iscsi/acceptor.c @@ -0,0 +1,91 @@ +/*- + * 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/thread.h" +#include "spdk/log.h" +#include "spdk/sock.h" +#include "spdk/string.h" +#include "iscsi/acceptor.h" +#include "iscsi/conn.h" +#include "iscsi/portal_grp.h" + +#define ACCEPT_TIMEOUT_US 1000 /* 1ms */ + +static int +spdk_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 = spdk_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; +} + +void +spdk_iscsi_acceptor_start(struct spdk_iscsi_portal *p) +{ + p->acceptor_poller = spdk_poller_register(spdk_iscsi_portal_accept, p, ACCEPT_TIMEOUT_US); +} + +void +spdk_iscsi_acceptor_stop(struct spdk_iscsi_portal *p) +{ + spdk_poller_unregister(&p->acceptor_poller); +} diff --git a/src/spdk/lib/iscsi/acceptor.h b/src/spdk/lib/iscsi/acceptor.h new file mode 100644 index 00000000..9060ee7d --- /dev/null +++ b/src/spdk/lib/iscsi/acceptor.h @@ -0,0 +1,43 @@ +/*- + * 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_ACCEPTOR_H_ +#define SPDK_ACCEPTOR_H_ + +struct spdk_iscsi_portal; + +void spdk_iscsi_acceptor_start(struct spdk_iscsi_portal *p); +void spdk_iscsi_acceptor_stop(struct spdk_iscsi_portal *p); + +#endif /* SPDK_ACCEPTOR_H_ */ diff --git a/src/spdk/lib/iscsi/conn.c b/src/spdk/lib/iscsi/conn.c new file mode 100644 index 00000000..d5cd5d1e --- /dev/null +++ b/src/spdk/lib/iscsi/conn.c @@ -0,0 +1,1470 @@ +/*- + * 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/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 SPDK_ISCSI_CONNECTION_MEMSET(conn) \ + memset(&(conn)->portal, 0, sizeof(*(conn)) - \ + offsetof(struct spdk_iscsi_conn, portal)); + +static int g_connections_per_lcore; +static uint32_t *g_num_connections; + +struct spdk_iscsi_conn *g_conns_array = MAP_FAILED; +static int g_conns_array_fd = -1; +static char g_shm_name[64]; + +static pthread_mutex_t g_conns_mutex = PTHREAD_MUTEX_INITIALIZER; + +static struct spdk_poller *g_shutdown_timer = NULL; + +static uint32_t spdk_iscsi_conn_allocate_reactor(const struct spdk_cpuset *cpumask); + +static void spdk_iscsi_conn_full_feature_migrate(void *arg1, void *arg2); +static void spdk_iscsi_conn_stop(struct spdk_iscsi_conn *conn); +static void spdk_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; + int i; + + pthread_mutex_lock(&g_conns_mutex); + for (i = 0; i < MAX_ISCSI_CONNECTIONS; i++) { + conn = &g_conns_array[i]; + if (!conn->is_valid) { + SPDK_ISCSI_CONNECTION_MEMSET(conn); + conn->is_valid = 1; + pthread_mutex_unlock(&g_conns_mutex); + return conn; + } + } + pthread_mutex_unlock(&g_conns_mutex); + + return NULL; +} + +static void +free_conn(struct spdk_iscsi_conn *conn) +{ + free(conn->portal_host); + free(conn->portal_port); + conn->is_valid = 0; +} + +static struct spdk_iscsi_conn * +spdk_find_iscsi_connection_by_id(int cid) +{ + if (g_conns_array[cid].is_valid == 1) { + return &g_conns_array[cid]; + } else { + return NULL; + } +} + +int spdk_initialize_iscsi_conns(void) +{ + size_t conns_size = sizeof(struct spdk_iscsi_conn) * MAX_ISCSI_CONNECTIONS; + uint32_t i, last_core; + + 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) { + fprintf(stderr, "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; + } + + last_core = spdk_env_get_last_core(); + g_num_connections = calloc(last_core + 1, sizeof(uint32_t)); + if (!g_num_connections) { + SPDK_ERRLOG("Could not allocate array size=%u for g_num_connections\n", + last_core + 1); + goto err; + } + + return 0; + +err: + if (g_conns_array != MAP_FAILED) { + munmap(g_conns_array, conns_size); + 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); + } + + return -1; +} + +static void +spdk_iscsi_poll_group_add_conn_sock(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_poll_group *poll_group; + int rc; + + assert(conn->lcore == spdk_env_get_current_core()); + + poll_group = &g_spdk_iscsi.poll_group[conn->lcore]; + + rc = spdk_sock_group_add_sock(poll_group->sock_group, conn->sock, spdk_iscsi_conn_sock_cb, conn); + if (rc < 0) { + SPDK_ERRLOG("Failed to add sock=%p of conn=%p\n", conn->sock, conn); + } +} + +static void +spdk_iscsi_poll_group_remove_conn_sock(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_poll_group *poll_group; + int rc; + + assert(conn->lcore == spdk_env_get_current_core()); + + poll_group = &g_spdk_iscsi.poll_group[conn->lcore]; + + rc = spdk_sock_group_remove_sock(poll_group->sock_group, conn->sock); + if (rc < 0) { + SPDK_ERRLOG("Failed to remove sock=%p of conn=%p\n", conn->sock, conn); + } +} + +static void +spdk_iscsi_poll_group_add_conn(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_poll_group *poll_group; + + assert(conn->lcore == spdk_env_get_current_core()); + + poll_group = &g_spdk_iscsi.poll_group[conn->lcore]; + + conn->is_stopped = false; + STAILQ_INSERT_TAIL(&poll_group->connections, conn, link); + spdk_iscsi_poll_group_add_conn_sock(conn); +} + +static void +spdk_iscsi_poll_group_remove_conn(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_poll_group *poll_group; + + assert(conn->lcore == spdk_env_get_current_core()); + + poll_group = &g_spdk_iscsi.poll_group[conn->lcore]; + + conn->is_stopped = true; + STAILQ_REMOVE(&poll_group->connections, conn, spdk_iscsi_conn, link); +} + +/** + * \brief Create an iSCSI connection from the given parameters and schedule it + * on a reactor. + * + * \code + * + * # identify reactor where the new connections work item will be scheduled + * reactor = spdk_iscsi_conn_allocate_reactor() + * allocate spdk_iscsi_conn object + * initialize spdk_iscsi_conn object + * schedule iSCSI connection work item on reactor + * + * \endcode + */ +int +spdk_iscsi_conn_construct(struct spdk_iscsi_portal *portal, + struct spdk_sock *sock) +{ + struct spdk_iscsi_conn *conn; + int bufsize, i, rc; + + conn = allocate_conn(); + if (conn == NULL) { + SPDK_ERRLOG("Could not allocate connection.\n"); + return -1; + } + + pthread_mutex_lock(&g_spdk_iscsi.mutex); + conn->timeout = g_spdk_iscsi.timeout; + conn->nopininterval = g_spdk_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; + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + conn->MaxRecvDataSegmentLength = 8192; // RFC3720(12.12) + + conn->portal = portal; + conn->pg_tag = portal->group->tag; + conn->portal_host = strdup(portal->host); + conn->portal_port = strdup(portal->port); + conn->portal_cpumask = portal->cpumask; + 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; + } + + for (i = 0; i < DEFAULT_MAXR2T; i++) { + conn->outstanding_r2t_tasks[i] = NULL; + } + + 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->open_lun_descs, 0, sizeof(conn->open_lun_descs)); + + 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; + } + + bufsize = 2 * 1024 * 1024; + rc = spdk_sock_set_recvbuf(conn->sock, bufsize); + if (rc != 0) { + SPDK_ERRLOG("spdk_sock_set_recvbuf failed\n"); + } + + bufsize = 32 * 1024 * 1024 / g_spdk_iscsi.MaxConnections; + if (bufsize > 2 * 1024 * 1024) { + bufsize = 2 * 1024 * 1024; + } + rc = spdk_sock_set_sendbuf(conn->sock, bufsize); + if (rc != 0) { + SPDK_ERRLOG("spdk_sock_set_sendbuf failed\n"); + } + + /* 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 = spdk_iscsi_conn_params_init(&conn->params); + if (rc < 0) { + SPDK_ERRLOG("iscsi_conn_params_init() failed\n"); +error_return: + spdk_iscsi_param_free(conn->params); + free_conn(conn); + return -1; + } + conn->logout_timer = NULL; + conn->shutdown_timer = NULL; + SPDK_NOTICELOG("Launching connection on acceptor thread\n"); + conn->pending_task_cnt = 0; + conn->pending_activate_event = false; + + conn->lcore = spdk_env_get_current_core(); + __sync_fetch_and_add(&g_num_connections[conn->lcore], 1); + + spdk_iscsi_poll_group_add_conn(conn); + return 0; +} + +void +spdk_iscsi_conn_free_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + if (pdu->task) { + if (pdu->bhs.opcode == ISCSI_OP_SCSI_DATAIN) { + if (pdu->task->scsi.offset > 0) { + conn->data_in_cnt--; + if (pdu->bhs.flags & ISCSI_DATAIN_STATUS) { + /* Free the primary task after the last subtask done */ + conn->data_in_cnt--; + spdk_iscsi_task_put(spdk_iscsi_task_get_primary(pdu->task)); + } + } + } else if (pdu->bhs.opcode == ISCSI_OP_SCSI_RSP && + pdu->task->scsi.status != SPDK_SCSI_STATUS_GOOD) { + if (pdu->task->scsi.offset > 0) { + spdk_iscsi_task_put(spdk_iscsi_task_get_primary(pdu->task)); + } + } + spdk_iscsi_task_put(pdu->task); + } + spdk_put_pdu(pdu); +} + +static int spdk_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->write_pdu_list, tailq, tmp_pdu) { + TAILQ_REMOVE(&conn->write_pdu_list, pdu, tailq); + spdk_iscsi_conn_free_pdu(conn, pdu); + } + + TAILQ_FOREACH_SAFE(pdu, &conn->snack_pdu_list, tailq, tmp_pdu) { + TAILQ_REMOVE(&conn->snack_pdu_list, pdu, tailq); + if (pdu->task) { + spdk_iscsi_task_put(pdu->task); + } + spdk_put_pdu(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); + spdk_iscsi_task_put(iscsi_task); + } + } + + if (conn->pending_task_cnt) { + return -1; + } + + return 0; +} + +static void spdk_iscsi_conn_free(struct spdk_iscsi_conn *conn) +{ + if (conn == NULL) { + return; + } + + spdk_iscsi_param_free(conn->params); + + /* + * Each connection pre-allocates its next PDU - make sure these get + * freed here. + */ + spdk_put_pdu(conn->pdu_in_progress); + + free_conn(conn); +} + +static void spdk_iscsi_remove_conn(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_sess *sess; + int idx; + uint32_t i, j; + + idx = -1; + sess = conn->sess; + conn->sess = NULL; + if (sess == NULL) { + spdk_iscsi_conn_free(conn); + return; + } + + for (i = 0; i < sess->connections; i++) { + if (sess->conns[i] == conn) { + idx = i; + break; + } + } + + if (sess->connections < 1) { + SPDK_ERRLOG("zero connection\n"); + sess->connections = 0; + } else { + if (idx < 0) { + SPDK_ERRLOG("remove conn not found\n"); + } else { + for (j = idx; j < sess->connections - 1; j++) { + sess->conns[j] = sess->conns[j + 1]; + } + sess->conns[sess->connections - 1] = NULL; + } + sess->connections--; + } + + SPDK_NOTICELOG("Terminating connections(tsih %d): %d\n", sess->tsih, sess->connections); + + if (sess->connections == 0) { + /* cleanup last connection */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "cleanup last conn free sess\n"); + spdk_free_sess(sess); + } + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "cleanup free conn\n"); + spdk_iscsi_conn_free(conn); +} + +static void +spdk_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_spdk_iscsi.AllowDuplicateIsid) { + /* clean up all tasks to all LUNs for session */ + target = conn->sess->target; + if (target != NULL) { + rc = spdk_iscsi_tgt_node_cleanup_luns(conn, target); + if (rc < 0) { + SPDK_ERRLOG("target abort failed\n"); + } + } + } +} + +static void +_spdk_iscsi_conn_free(struct spdk_iscsi_conn *conn) +{ + pthread_mutex_lock(&g_conns_mutex); + spdk_iscsi_remove_conn(conn); + pthread_mutex_unlock(&g_conns_mutex); +} + +static int +_spdk_iscsi_conn_check_shutdown(void *arg) +{ + struct spdk_iscsi_conn *conn = arg; + int rc; + + rc = spdk_iscsi_conn_free_tasks(conn); + if (rc < 0) { + return -1; + } + + spdk_poller_unregister(&conn->shutdown_timer); + + spdk_iscsi_conn_stop(conn); + _spdk_iscsi_conn_free(conn); + + return -1; +} + +static void +_spdk_iscsi_conn_destruct(struct spdk_iscsi_conn *conn) +{ + int rc; + + spdk_clear_all_transfer_task(conn, NULL); + spdk_iscsi_poll_group_remove_conn_sock(conn); + spdk_sock_close(&conn->sock); + spdk_poller_unregister(&conn->logout_timer); + spdk_poller_unregister(&conn->flush_poller); + + rc = spdk_iscsi_conn_free_tasks(conn); + if (rc < 0) { + /* The connection cannot be freed yet. Check back later. */ + conn->shutdown_timer = spdk_poller_register(_spdk_iscsi_conn_check_shutdown, conn, 1000); + } else { + spdk_iscsi_conn_stop(conn); + _spdk_iscsi_conn_free(conn); + } +} + +static int +_spdk_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)) { + return -1; + } + + spdk_poller_unregister(&conn->shutdown_timer); + + _spdk_iscsi_conn_destruct(conn); + + return -1; +} + +void +spdk_iscsi_conn_destruct(struct spdk_iscsi_conn *conn) +{ + conn->state = ISCSI_CONN_STATE_EXITED; + + if (conn->sess != NULL && conn->pending_task_cnt > 0) { + spdk_iscsi_conn_cleanup_backend(conn); + } + + if (conn->dev != NULL && spdk_scsi_dev_has_pending_tasks(conn->dev)) { + conn->shutdown_timer = spdk_poller_register(_spdk_iscsi_conn_check_pending_tasks, conn, 1000); + } else { + _spdk_iscsi_conn_destruct(conn); + } +} + +static int +spdk_iscsi_get_active_conns(void) +{ + struct spdk_iscsi_conn *conn; + int num = 0; + int i; + + pthread_mutex_lock(&g_conns_mutex); + for (i = 0; i < MAX_ISCSI_CONNECTIONS; i++) { + conn = spdk_find_iscsi_connection_by_id(i); + if (conn == NULL) { + continue; + } + num++; + } + pthread_mutex_unlock(&g_conns_mutex); + return num; +} + +static void +spdk_iscsi_conns_cleanup(void) +{ + free(g_num_connections); + munmap(g_conns_array, sizeof(struct spdk_iscsi_conn) * + MAX_ISCSI_CONNECTIONS); + shm_unlink(g_shm_name); + if (g_conns_array_fd >= 0) { + close(g_conns_array_fd); + g_conns_array_fd = -1; + } +} + +static void +spdk_iscsi_conn_check_shutdown_cb(void *arg1, void *arg2) +{ + spdk_iscsi_conns_cleanup(); + spdk_shutdown_iscsi_conns_done(); +} + +static int +spdk_iscsi_conn_check_shutdown(void *arg) +{ + struct spdk_event *event; + + if (spdk_iscsi_get_active_conns() == 0) { + spdk_poller_unregister(&g_shutdown_timer); + event = spdk_event_allocate(spdk_env_get_current_core(), + spdk_iscsi_conn_check_shutdown_cb, NULL, NULL); + spdk_event_call(event); + } + + return -1; +} + +static void +spdk_iscsi_conn_close_lun(struct spdk_iscsi_conn *conn, int lun_id) +{ + struct spdk_scsi_desc *desc; + + desc = conn->open_lun_descs[lun_id]; + if (desc != NULL) { + spdk_scsi_lun_free_io_channel(desc); + spdk_scsi_lun_close(desc); + conn->open_lun_descs[lun_id] = NULL; + } +} + +static void +spdk_iscsi_conn_close_luns(struct spdk_iscsi_conn *conn) +{ + int i; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + spdk_iscsi_conn_close_lun(conn, i); + } +} + +static void +_iscsi_conn_remove_lun(void *arg1, void *arg2) +{ + struct spdk_iscsi_conn *conn = arg1; + struct spdk_scsi_lun *lun = arg2; + int lun_id = spdk_scsi_lun_get_id(lun); + struct spdk_iscsi_pdu *pdu, *tmp_pdu; + struct spdk_iscsi_task *iscsi_task, *tmp_iscsi_task; + + /* If a connection is already in stating status, just return */ + if (conn->state >= ISCSI_CONN_STATE_EXITING) { + return; + } + + spdk_clear_all_transfer_task(conn, lun); + TAILQ_FOREACH_SAFE(pdu, &conn->write_pdu_list, tailq, tmp_pdu) { + if (pdu->task && (lun == pdu->task->scsi.lun)) { + TAILQ_REMOVE(&conn->write_pdu_list, pdu, tailq); + spdk_iscsi_conn_free_pdu(conn, pdu); + } + } + + TAILQ_FOREACH_SAFE(pdu, &conn->snack_pdu_list, tailq, tmp_pdu) { + if (pdu->task && (lun == pdu->task->scsi.lun)) { + TAILQ_REMOVE(&conn->snack_pdu_list, pdu, tailq); + spdk_iscsi_task_put(pdu->task); + spdk_put_pdu(pdu); + } + } + + TAILQ_FOREACH_SAFE(iscsi_task, &conn->queued_datain_tasks, link, tmp_iscsi_task) { + if ((!iscsi_task->is_queued) && (lun == iscsi_task->scsi.lun)) { + TAILQ_REMOVE(&conn->queued_datain_tasks, iscsi_task, link); + spdk_iscsi_task_put(iscsi_task); + } + } + + spdk_iscsi_conn_close_lun(conn, lun_id); +} + +static void +spdk_iscsi_conn_remove_lun(struct spdk_scsi_lun *lun, void *remove_ctx) +{ + struct spdk_iscsi_conn *conn = remove_ctx; + struct spdk_event *event; + + event = spdk_event_allocate(conn->lcore, _iscsi_conn_remove_lun, + conn, lun); + spdk_event_call(event); +} + +static void +spdk_iscsi_conn_open_luns(struct spdk_iscsi_conn *conn) +{ + int i, rc; + struct spdk_scsi_lun *lun; + struct spdk_scsi_desc *desc; + + for (i = 0; i < SPDK_SCSI_DEV_MAX_LUN; i++) { + lun = spdk_scsi_dev_get_lun(conn->dev, i); + if (lun == NULL) { + continue; + } + + rc = spdk_scsi_lun_open(lun, spdk_iscsi_conn_remove_lun, conn, &desc); + if (rc != 0) { + goto error; + } + + rc = spdk_scsi_lun_allocate_io_channel(desc); + if (rc != 0) { + spdk_scsi_lun_close(desc); + goto error; + } + + conn->open_lun_descs[i] = desc; + } + + return; + +error: + spdk_iscsi_conn_close_luns(conn); +} + +/** + * This function will stop executing the specified connection. + */ +static void +spdk_iscsi_conn_stop(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_tgt_node *target; + + if (conn->state == ISCSI_CONN_STATE_EXITED && 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); + + spdk_iscsi_conn_close_luns(conn); + } + + assert(conn->lcore == spdk_env_get_current_core()); + + __sync_fetch_and_sub(&g_num_connections[conn->lcore], 1); + spdk_iscsi_poll_group_remove_conn(conn); +} + +void spdk_shutdown_iscsi_conns(void) +{ + struct spdk_iscsi_conn *conn; + int i; + + pthread_mutex_lock(&g_conns_mutex); + + for (i = 0; i < MAX_ISCSI_CONNECTIONS; i++) { + conn = spdk_find_iscsi_connection_by_id(i); + if (conn == NULL) { + continue; + } + + /* 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. + */ + if (conn->state < ISCSI_CONN_STATE_EXITING) { + conn->state = ISCSI_CONN_STATE_EXITING; + } + } + + pthread_mutex_unlock(&g_conns_mutex); + g_shutdown_timer = spdk_poller_register(spdk_iscsi_conn_check_shutdown, NULL, + 1000); +} + +int +spdk_iscsi_drop_conns(struct spdk_iscsi_conn *conn, const char *conn_match, + int drop_all) +{ + struct spdk_iscsi_conn *xconn; + const char *xconn_match; + int i, num; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_iscsi_drop_conns\n"); + + num = 0; + pthread_mutex_lock(&g_conns_mutex); + for (i = 0; i < MAX_ISCSI_CONNECTIONS; i++) { + xconn = spdk_find_iscsi_connection_by_id(i); + + if (xconn == NULL) { + continue; + } + + 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); + + /* Do not set xconn->state if the connection has already started exiting. + * This ensures we do not move a connection from EXITED state back to EXITING. + */ + if (xconn->state < ISCSI_CONN_STATE_EXITING) { + xconn->state = ISCSI_CONN_STATE_EXITING; + } + num++; + } + } + + pthread_mutex_unlock(&g_conns_mutex); + + if (num != 0) { + SPDK_ERRLOG("exiting %d conns\n", num); + } + + return 0; +} + +/** + * \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 +spdk_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; +} + +void +spdk_iscsi_task_mgmt_cpl(struct spdk_scsi_task *scsi_task) +{ + struct spdk_iscsi_task *task = spdk_iscsi_task_from_scsi_task(scsi_task); + + spdk_iscsi_task_mgmt_response(task->conn, task); + spdk_iscsi_task_put(task); +} + +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; + spdk_iscsi_task_response(conn, subtask); + spdk_iscsi_task_put(subtask); + } else { + break; + } + } +} + +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 (task->scsi.status != SPDK_SCSI_STATUS_GOOD) { + TAILQ_FOREACH(tmp, &primary->subtask_list, subtask_link) { + spdk_scsi_task_copy_status(&tmp->scsi, &task->scsi); + } + } + + if ((task != primary) && + (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); + return; + } + + primary->bytes_completed += task->scsi.length; + spdk_iscsi_task_response(conn, task); + + if ((task != primary) || + (task->scsi.transfer_len == task->scsi.length)) { + spdk_iscsi_task_put(task); + } + process_completed_read_subtask_list(conn, primary); + + spdk_iscsi_conn_handle_queued_datain_tasks(conn); +} + +void +spdk_iscsi_task_cpl(struct spdk_scsi_task *scsi_task) +{ + struct spdk_iscsi_task *primary; + struct spdk_iscsi_task *task = spdk_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 = spdk_iscsi_task_get_primary(task); + + if (spdk_iscsi_task_is_read(primary)) { + process_read_task_completion(conn, task, primary); + } else { + primary->bytes_completed += task->scsi.length; + if (task != primary) { + if (task->scsi.status == SPDK_SCSI_STATUS_GOOD) { + primary->scsi.data_transferred += task->scsi.data_transferred; + } else { + spdk_scsi_task_copy_status(&primary->scsi, &task->scsi); + } + } + + if (primary->bytes_completed == primary->scsi.transfer_len) { + spdk_del_transfer_task(conn, primary->tag); + spdk_iscsi_task_response(conn, primary); + /* + * 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) { + TAILQ_REMOVE(&conn->active_r2t_tasks, primary, link); + spdk_iscsi_task_put(primary); + } + } + spdk_iscsi_task_put(task); + } + if (!task->parent) { + spdk_trace_record(TRACE_ISCSI_PDU_COMPLETED, 0, 0, (uintptr_t)pdu, 0); + } +} + +static int +spdk_iscsi_get_pdu_length(struct spdk_iscsi_pdu *pdu, int header_digest, + int data_digest) +{ + int data_len, enable_digest, total; + + enable_digest = 1; + if (pdu->bhs.opcode == ISCSI_OP_LOGIN_RSP) { + enable_digest = 0; + } + + total = ISCSI_BHS_LEN; + + total += (4 * pdu->bhs.total_ahs_len); + + if (enable_digest && header_digest) { + total += ISCSI_DIGEST_LEN; + } + + data_len = DGET24(pdu->bhs.data_segment_len); + if (data_len > 0) { + total += ISCSI_ALIGN(data_len); + if (enable_digest && data_digest) { + total += ISCSI_DIGEST_LEN; + } + } + + return total; +} + +void +spdk_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_get_ticks_hz())) { + 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) { + spdk_iscsi_send_nopin(conn); + } +} + +/** + * \brief Makes one attempt to flush response PDUs back to the initiator. + * + * Builds a list of iovecs for response PDUs that must be sent back to the + * initiator and passes it to writev(). + * + * Since the socket is non-blocking, writev() may not be able to flush all + * of the iovecs, and may even partially flush one of the iovecs. In this + * case, the partially flushed PDU will remain on the write_pdu_list with + * an offset pointing to the next byte to be flushed. + * + * Returns 0 if all PDUs were flushed. + * + * Returns 1 if some PDUs could not be flushed due to lack of send buffer + * space. + * + * Returns -1 if an exception error occurred indicating the TCP connection + * should be closed. + */ +static int +spdk_iscsi_conn_flush_pdus_internal(struct spdk_iscsi_conn *conn) +{ + const int array_size = 32; + struct iovec iovec_array[array_size]; + struct iovec *iov = iovec_array; + int iovec_cnt = 0; + int bytes = 0; + int total_length = 0; + uint32_t writev_offset; + struct spdk_iscsi_pdu *pdu; + int pdu_length; + + pdu = TAILQ_FIRST(&conn->write_pdu_list); + + if (pdu == NULL) { + return 0; + } + + /* + * Build up a list of iovecs for the first few PDUs in the + * connection's write_pdu_list. + */ + while (pdu != NULL && ((array_size - iovec_cnt) >= 5)) { + pdu_length = spdk_iscsi_get_pdu_length(pdu, + conn->header_digest, + conn->data_digest); + iovec_cnt += spdk_iscsi_build_iovecs(conn, + &iovec_array[iovec_cnt], + pdu); + total_length += pdu_length; + pdu = TAILQ_NEXT(pdu, tailq); + } + + /* + * Check if the first PDU was partially written out the last time + * this function was called, and if so adjust the iovec array + * accordingly. + */ + writev_offset = TAILQ_FIRST(&conn->write_pdu_list)->writev_offset; + total_length -= writev_offset; + while (writev_offset > 0) { + if (writev_offset >= iov->iov_len) { + writev_offset -= iov->iov_len; + iov++; + iovec_cnt--; + } else { + iov->iov_len -= writev_offset; + iov->iov_base = (char *)iov->iov_base + writev_offset; + writev_offset = 0; + } + } + + spdk_trace_record(TRACE_ISCSI_FLUSH_WRITEBUF_START, conn->id, total_length, 0, iovec_cnt); + + bytes = spdk_sock_writev(conn->sock, iov, iovec_cnt); + if (bytes == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return 1; + } else { + SPDK_ERRLOG("spdk_sock_writev() failed, errno %d: %s\n", + errno, spdk_strerror(errno)); + return -1; + } + } + + spdk_trace_record(TRACE_ISCSI_FLUSH_WRITEBUF_DONE, conn->id, bytes, 0, 0); + + pdu = TAILQ_FIRST(&conn->write_pdu_list); + + /* + * Free any PDUs that were fully written. If a PDU was only + * partially written, update its writev_offset so that next + * time only the unwritten portion will be sent to writev(). + */ + while (bytes > 0) { + pdu_length = spdk_iscsi_get_pdu_length(pdu, + conn->header_digest, + conn->data_digest); + pdu_length -= pdu->writev_offset; + + if (bytes >= pdu_length) { + bytes -= pdu_length; + TAILQ_REMOVE(&conn->write_pdu_list, pdu, tailq); + + if ((conn->full_feature) && + (conn->sess->ErrorRecoveryLevel >= 1) && + spdk_iscsi_is_deferred_free_pdu(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 { + spdk_iscsi_conn_free_pdu(conn, pdu); + } + + pdu = TAILQ_FIRST(&conn->write_pdu_list); + } else { + pdu->writev_offset += bytes; + bytes = 0; + } + } + + return TAILQ_EMPTY(&conn->write_pdu_list) ? 0 : 1; +} + +/** + * \brief Flushes response PDUs back to the initiator. + * + * This function may return without all PDUs having flushed to the + * underlying TCP socket buffer - for example, in the case where the + * socket buffer is already full. + * + * During normal RUNNING connection state, if not all PDUs are flushed, + * then subsequent calls to this routine will eventually flush + * remaining PDUs. + * + * During other connection states (EXITING or LOGGED_OUT), this + * function will spin until all PDUs have successfully been flushed. + * + * Returns 0 for success and when all PDUs were able to be flushed. + * + * Returns 1 for success but when some PDUs could not be flushed due + * to lack of TCP buffer space. + * + * Returns -1 for an exceptional error indicating the TCP connection + * should be closed. + */ +static int +spdk_iscsi_conn_flush_pdus(void *_conn) +{ + struct spdk_iscsi_conn *conn = _conn; + int rc; + + if (conn->state == ISCSI_CONN_STATE_RUNNING) { + rc = spdk_iscsi_conn_flush_pdus_internal(conn); + if (rc == 0 && conn->flush_poller != NULL) { + spdk_poller_unregister(&conn->flush_poller); + } else if (rc == 1 && conn->flush_poller == NULL) { + conn->flush_poller = spdk_poller_register(spdk_iscsi_conn_flush_pdus, conn, 50); + } + } else { + /* + * If the connection state is not RUNNING, then + * keep trying to flush PDUs until our list is + * empty - to make sure all data is sent before + * closing the connection. + */ + do { + rc = spdk_iscsi_conn_flush_pdus_internal(conn); + } while (rc == 1); + } + + if (rc < 0 && conn->state < ISCSI_CONN_STATE_EXITING) { + /* + * If the poller has already started destruction of the connection, + * i.e. the socket read failed, then the connection state may already + * be EXITED. We don't want to set it back to EXITING in that case. + */ + conn->state = ISCSI_CONN_STATE_EXITING; + } + + return -1; +} + +void +spdk_iscsi_conn_write_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + TAILQ_INSERT_TAIL(&conn->write_pdu_list, pdu, tailq); + spdk_iscsi_conn_flush_pdus(conn); +} + +#define GET_PDU_LOOP_COUNT 16 + +static int +spdk_iscsi_conn_handle_incoming_pdus(struct spdk_iscsi_conn *conn) +{ + struct spdk_iscsi_pdu *pdu; + int i, rc; + + /* Read new PDUs from network */ + for (i = 0; i < GET_PDU_LOOP_COUNT; i++) { + rc = spdk_iscsi_read_pdu(conn, &pdu); + if (rc == 0) { + break; + } else if (rc == SPDK_ISCSI_CONNECTION_FATAL) { + return rc; + } + + if (conn->state == ISCSI_CONN_STATE_LOGGED_OUT) { + SPDK_ERRLOG("pdu received after logout\n"); + spdk_put_pdu(pdu); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + rc = spdk_iscsi_execute(conn, pdu); + spdk_put_pdu(pdu); + if (rc != 0) { + SPDK_ERRLOG("spdk_iscsi_execute() fatal error on %s(%s)\n", + 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; + } + + spdk_trace_record(TRACE_ISCSI_TASK_EXECUTED, 0, 0, (uintptr_t)pdu, 0); + if (conn->is_stopped) { + break; + } + } + + return i; +} + +static void +spdk_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 = spdk_iscsi_conn_handle_incoming_pdus(conn); + if (rc < 0) { + conn->state = ISCSI_CONN_STATE_EXITING; + spdk_iscsi_conn_flush_pdus(conn); + } +} + +static void +spdk_iscsi_conn_full_feature_migrate(void *arg1, void *arg2) +{ + struct spdk_iscsi_conn *conn = arg1; + + if (conn->sess->session_type == SESSION_TYPE_NORMAL) { + spdk_iscsi_conn_open_luns(conn); + } + + /* The poller has been unregistered, so now we can re-register it on the new core. */ + conn->lcore = spdk_env_get_current_core(); + spdk_iscsi_poll_group_add_conn(conn); +} + +void +spdk_iscsi_conn_migration(struct spdk_iscsi_conn *conn) +{ + int lcore; + struct spdk_event *event; + struct spdk_iscsi_tgt_node *target; + + lcore = spdk_iscsi_conn_allocate_reactor(conn->portal->cpumask); + if (conn->sess->session_type == SESSION_TYPE_NORMAL) { + 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. + * Save the lcore in the target node so it can be used for + * any other connections to this target node. + */ + target->lcore = lcore; + } else { + /** + * There are other active connections for this target node. + * Ignore the lcore specified by the allocator and use the + * the target node's lcore to ensure this connection runs on + * the same lcore as other connections for this target node. + */ + lcore = target->lcore; + } + pthread_mutex_unlock(&target->mutex); + } + + spdk_iscsi_poll_group_remove_conn_sock(conn); + spdk_poller_unregister(&conn->flush_poller); + spdk_iscsi_conn_stop(conn); + + __sync_fetch_and_add(&g_num_connections[lcore], 1); + conn->last_nopin = spdk_get_ticks(); + event = spdk_event_allocate(lcore, spdk_iscsi_conn_full_feature_migrate, + conn, NULL); + spdk_event_call(event); +} + +void +spdk_iscsi_conn_set_min_per_core(int count) +{ + g_connections_per_lcore = count; +} + +int +spdk_iscsi_conn_get_min_per_core(void) +{ + return g_connections_per_lcore; +} + +static uint32_t +spdk_iscsi_conn_allocate_reactor(const struct spdk_cpuset *cpumask) +{ + uint32_t i, selected_core; + int32_t num_pollers, min_pollers; + + min_pollers = INT_MAX; + selected_core = spdk_env_get_first_core(); + + SPDK_ENV_FOREACH_CORE(i) { + if (!spdk_cpuset_get_cpu(cpumask, i)) { + continue; + } + + /* This core is running. Check how many pollers it already has. */ + num_pollers = g_num_connections[i]; + + if ((num_pollers > 0) && (num_pollers < g_connections_per_lcore)) { + /* Fewer than the maximum connections per core, + * but at least 1. Use this core. + */ + return i; + } else if (num_pollers < min_pollers) { + /* Track the core that has the minimum number of pollers + * to be used if no cores meet our criteria + */ + selected_core = i; + min_pollers = num_pollers; + } + } + + return selected_core; +} + +static int +logout_timeout(void *arg) +{ + struct spdk_iscsi_conn *conn = arg; + + spdk_iscsi_conn_destruct(conn); + + return -1; +} + +void +spdk_iscsi_conn_logout(struct spdk_iscsi_conn *conn) +{ + conn->state = ISCSI_CONN_STATE_LOGGED_OUT; + conn->logout_timer = spdk_poller_register(logout_timeout, conn, ISCSI_LOGOUT_TIMEOUT * 1000000); +} + +SPDK_TRACE_REGISTER_FN(iscsi_conn_trace) +{ + spdk_trace_register_owner(OWNER_ISCSI_CONN, 'c'); + spdk_trace_register_object(OBJECT_ISCSI_PDU, 'p'); + spdk_trace_register_description("ISCSI_READ_FROM_SOCKET_DONE", "", + TRACE_ISCSI_READ_FROM_SOCKET_DONE, + OWNER_ISCSI_CONN, OBJECT_NONE, 0, 0, ""); + spdk_trace_register_description("ISCSI_FLUSH_WRITEBUF_START", "", TRACE_ISCSI_FLUSH_WRITEBUF_START, + OWNER_ISCSI_CONN, OBJECT_NONE, 0, 0, "iovec: "); + spdk_trace_register_description("ISCSI_FLUSH_WRITEBUF_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, ""); +} diff --git a/src/spdk/lib/iscsi/conn.h b/src/spdk/lib/iscsi/conn.h new file mode 100644 index 00000000..4a91e698 --- /dev/null +++ b/src/spdk/lib/iscsi/conn.h @@ -0,0 +1,193 @@ +/*- + * 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) + +struct spdk_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; + char *portal_port; + struct spdk_cpuset *portal_cpumask; + uint32_t lcore; + struct spdk_sock *sock; + struct spdk_iscsi_sess *sess; + + enum iscsi_connection_state state; + int login_phase; + + uint64_t last_flush; + uint64_t last_fill; + uint64_t last_nopin; + + /* 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; + + TAILQ_HEAD(, spdk_iscsi_pdu) write_pdu_list; + TAILQ_HEAD(, spdk_iscsi_pdu) snack_pdu_list; + + int pending_r2t; + struct spdk_iscsi_task *outstanding_r2t_tasks[DEFAULT_MAXR2T]; + + 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; + int authenticated; + int req_auth; + int req_mutual; + uint32_t pending_task_cnt; + uint32_t data_out_cnt; + uint32_t data_in_cnt; + bool pending_activate_event; + + int 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) link; + struct spdk_poller *flush_poller; + 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_scsi_desc *open_lun_descs[SPDK_SCSI_DEV_MAX_LUN]; +}; + +extern struct spdk_iscsi_conn *g_conns_array; + +int spdk_initialize_iscsi_conns(void); +void spdk_shutdown_iscsi_conns(void); + +int spdk_iscsi_conn_construct(struct spdk_iscsi_portal *portal, struct spdk_sock *sock); +void spdk_iscsi_conn_destruct(struct spdk_iscsi_conn *conn); +void spdk_iscsi_conn_handle_nop(struct spdk_iscsi_conn *conn); +void spdk_iscsi_conn_migration(struct spdk_iscsi_conn *conn); +void spdk_iscsi_conn_logout(struct spdk_iscsi_conn *conn); +int spdk_iscsi_drop_conns(struct spdk_iscsi_conn *conn, + const char *conn_match, int drop_all); +void spdk_iscsi_conn_set_min_per_core(int count); +int spdk_iscsi_conn_get_min_per_core(void); + +int spdk_iscsi_conn_read_data(struct spdk_iscsi_conn *conn, int len, + void *buf); +void spdk_iscsi_conn_write_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu); + +void spdk_iscsi_conn_free_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu); + +#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 00000000..33b7bfc3 --- /dev/null +++ b/src/spdk/lib/iscsi/init_grp.c @@ -0,0 +1,786 @@ +/*- + * 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 * +spdk_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 * +spdk_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 +spdk_iscsi_init_grp_add_initiator(struct spdk_iscsi_init_grp *ig, char *name) +{ + struct spdk_iscsi_initiator_name *iname; + char *p; + + if (ig->ninitiators >= MAX_INITIATOR) { + SPDK_ERRLOG("> MAX_INITIATOR(=%d) is not allowed\n", MAX_INITIATOR); + return -EPERM; + } + + iname = spdk_iscsi_init_grp_find_initiator(ig, name); + if (iname != NULL) { + return -EEXIST; + } + + iname = malloc(sizeof(*iname)); + if (iname == NULL) { + SPDK_ERRLOG("malloc() failed for initiator name str\n"); + return -ENOMEM; + } + + iname->name = strdup(name); + if (iname->name == NULL) { + SPDK_ERRLOG("strdup() failed for initiator name\n"); + free(iname); + return -ENOMEM; + } + + /* 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 +spdk_iscsi_init_grp_delete_initiator(struct spdk_iscsi_init_grp *ig, char *name) +{ + struct spdk_iscsi_initiator_name *iname; + + iname = spdk_iscsi_init_grp_find_initiator(ig, name); + if (iname == NULL) { + return -ENOENT; + } + + TAILQ_REMOVE(&ig->initiator_head, iname, tailq); + ig->ninitiators--; + free(iname->name); + free(iname); + return 0; +} + +static int +spdk_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 = spdk_iscsi_init_grp_add_initiator(ig, inames[i]); + if (rc < 0) { + goto cleanup; + } + } + return 0; + +cleanup: + for (; i > 0; --i) { + spdk_iscsi_init_grp_delete_initiator(ig, inames[i - 1]); + } + return rc; +} + +static void +spdk_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->name); + free(iname); + } +} + +static int +spdk_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 = spdk_iscsi_init_grp_delete_initiator(ig, inames[i]); + if (rc < 0) { + goto cleanup; + } + } + return 0; + +cleanup: + for (; i > 0; --i) { + rc = spdk_iscsi_init_grp_add_initiator(ig, inames[i - 1]); + if (rc != 0) { + spdk_iscsi_init_grp_delete_all_initiators(ig); + break; + } + } + return -1; +} + +static struct spdk_iscsi_initiator_netmask * +spdk_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 +spdk_iscsi_init_grp_add_netmask(struct spdk_iscsi_init_grp *ig, char *mask) +{ + struct spdk_iscsi_initiator_netmask *imask; + char *p; + + if (ig->nnetmasks >= MAX_NETMASK) { + SPDK_ERRLOG("> MAX_NETMASK(=%d) is not allowed\n", MAX_NETMASK); + return -EPERM; + } + + imask = spdk_iscsi_init_grp_find_netmask(ig, mask); + if (imask != NULL) { + return -EEXIST; + } + + imask = malloc(sizeof(*imask)); + if (imask == NULL) { + SPDK_ERRLOG("malloc() failed for inititator mask str\n"); + return -ENOMEM; + } + + imask->mask = strdup(mask); + if (imask->mask == NULL) { + SPDK_ERRLOG("strdup() failed for initiator mask\n"); + free(imask); + return -ENOMEM; + } + + /* 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 +spdk_iscsi_init_grp_delete_netmask(struct spdk_iscsi_init_grp *ig, char *mask) +{ + struct spdk_iscsi_initiator_netmask *imask; + + imask = spdk_iscsi_init_grp_find_netmask(ig, mask); + if (imask == NULL) { + return -ENOENT; + } + + TAILQ_REMOVE(&ig->netmask_head, imask, tailq); + ig->nnetmasks--; + free(imask->mask); + free(imask); + return 0; +} + +static int +spdk_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 = spdk_iscsi_init_grp_add_netmask(ig, imasks[i]); + if (rc != 0) { + goto cleanup; + } + } + return 0; + +cleanup: + for (; i > 0; --i) { + spdk_iscsi_init_grp_delete_netmask(ig, imasks[i - 1]); + } + return rc; +} + +static void +spdk_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->mask); + free(imask); + } +} + +static int +spdk_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 = spdk_iscsi_init_grp_delete_netmask(ig, imasks[i]); + if (rc != 0) { + goto cleanup; + } + } + return 0; + +cleanup: + for (; i > 0; --i) { + rc = spdk_iscsi_init_grp_add_netmask(ig, imasks[i - 1]); + if (rc != 0) { + spdk_iscsi_init_grp_delete_all_netmasks(ig); + break; + } + } + return -1; +} + +/* Read spdk iscsi target's config file and create initiator group */ +static int +spdk_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 = spdk_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 +spdk_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_spdk_iscsi.mutex); + tmp = spdk_iscsi_init_grp_find_by_tag(ig->tag); + if (tmp == NULL) { + TAILQ_INSERT_TAIL(&g_spdk_iscsi.ig_head, ig, tailq); + rc = 0; + } + pthread_mutex_unlock(&g_spdk_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 +spdk_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 = spdk_iscsi_init_grp_create(tag); + if (!ig) { + SPDK_ERRLOG("initiator group create error (%d)\n", tag); + return rc; + } + + rc = spdk_iscsi_init_grp_add_initiators(ig, num_initiator_names, + initiator_names); + if (rc < 0) { + SPDK_ERRLOG("add initiator name error\n"); + goto cleanup; + } + + rc = spdk_iscsi_init_grp_add_netmasks(ig, num_initiator_masks, + initiator_masks); + if (rc < 0) { + SPDK_ERRLOG("add initiator netmask error\n"); + goto cleanup; + } + + rc = spdk_iscsi_init_grp_register(ig); + if (rc < 0) { + SPDK_ERRLOG("initiator group register error (%d)\n", tag); + goto cleanup; + } + return 0; + +cleanup: + spdk_iscsi_init_grp_destroy(ig); + return rc; +} + +int +spdk_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_spdk_iscsi.mutex); + ig = spdk_iscsi_init_grp_find_by_tag(tag); + if (!ig) { + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + SPDK_ERRLOG("initiator group (%d) is not found\n", tag); + return rc; + } + + rc = spdk_iscsi_init_grp_add_initiators(ig, num_initiator_names, + initiator_names); + if (rc < 0) { + SPDK_ERRLOG("add initiator name error\n"); + goto error; + } + + rc = spdk_iscsi_init_grp_add_netmasks(ig, num_initiator_masks, + initiator_masks); + if (rc < 0) { + SPDK_ERRLOG("add initiator netmask error\n"); + spdk_iscsi_init_grp_delete_initiators(ig, num_initiator_names, + initiator_names); + } + +error: + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + return rc; +} + +int +spdk_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_spdk_iscsi.mutex); + ig = spdk_iscsi_init_grp_find_by_tag(tag); + if (!ig) { + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + SPDK_ERRLOG("initiator group (%d) is not found\n", tag); + return rc; + } + + rc = spdk_iscsi_init_grp_delete_initiators(ig, num_initiator_names, + initiator_names); + if (rc < 0) { + SPDK_ERRLOG("delete initiator name error\n"); + goto error; + } + + rc = spdk_iscsi_init_grp_delete_netmasks(ig, num_initiator_masks, + initiator_masks); + if (rc < 0) { + SPDK_ERRLOG("delete initiator netmask error\n"); + spdk_iscsi_init_grp_add_initiators(ig, num_initiator_names, + initiator_names); + goto error; + } + +error: + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + return rc; +} + +void +spdk_iscsi_init_grp_destroy(struct spdk_iscsi_init_grp *ig) +{ + if (!ig) { + return; + } + + spdk_iscsi_init_grp_delete_all_initiators(ig); + spdk_iscsi_init_grp_delete_all_netmasks(ig); + free(ig); +}; + +struct spdk_iscsi_init_grp * +spdk_iscsi_init_grp_find_by_tag(int tag) +{ + struct spdk_iscsi_init_grp *ig; + + TAILQ_FOREACH(ig, &g_spdk_iscsi.ig_head, tailq) { + if (ig->tag == tag) { + return ig; + } + } + + return NULL; +} + +int +spdk_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 = spdk_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 +spdk_iscsi_init_grps_destroy(void) +{ + struct spdk_iscsi_init_grp *ig, *tmp; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_iscsi_init_grp_array_destroy\n"); + pthread_mutex_lock(&g_spdk_iscsi.mutex); + TAILQ_FOREACH_SAFE(ig, &g_spdk_iscsi.ig_head, tailq, tmp) { + TAILQ_REMOVE(&g_spdk_iscsi.ig_head, ig, tailq); + spdk_iscsi_init_grp_destroy(ig); + } + pthread_mutex_unlock(&g_spdk_iscsi.mutex); +} + +struct spdk_iscsi_init_grp * +spdk_iscsi_init_grp_unregister(int tag) +{ + struct spdk_iscsi_init_grp *ig; + + pthread_mutex_lock(&g_spdk_iscsi.mutex); + TAILQ_FOREACH(ig, &g_spdk_iscsi.ig_head, tailq) { + if (ig->tag == tag) { + TAILQ_REMOVE(&g_spdk_iscsi.ig_head, ig, tailq); + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + return ig; + } + } + pthread_mutex_unlock(&g_spdk_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 +spdk_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_spdk_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 +spdk_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 +spdk_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", "add_initiator_group"); + + spdk_json_write_name(w, "params"); + spdk_iscsi_init_grp_info_json(ig, w); + + spdk_json_write_object_end(w); +} + +void +spdk_iscsi_init_grps_info_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_init_grp *ig; + + TAILQ_FOREACH(ig, &g_spdk_iscsi.ig_head, tailq) { + spdk_iscsi_init_grp_info_json(ig, w); + } +} + +void +spdk_iscsi_init_grps_config_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_init_grp *ig; + + TAILQ_FOREACH(ig, &g_spdk_iscsi.ig_head, tailq) { + spdk_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 00000000..ff24ee5b --- /dev/null +++ b/src/spdk/lib/iscsi/init_grp.h @@ -0,0 +1,79 @@ +/*- + * 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" + +struct spdk_iscsi_initiator_name { + char *name; + TAILQ_ENTRY(spdk_iscsi_initiator_name) tailq; +}; + +struct spdk_iscsi_initiator_netmask { + char *mask; + 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 spdk_iscsi_init_grp_create_from_initiator_list(int tag, + int num_initiator_names, char **initiator_names, + int num_initiator_masks, char **initiator_masks); +int spdk_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 spdk_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 spdk_iscsi_init_grp_register(struct spdk_iscsi_init_grp *ig); +struct spdk_iscsi_init_grp *spdk_iscsi_init_grp_unregister(int tag); +struct spdk_iscsi_init_grp *spdk_iscsi_init_grp_find_by_tag(int tag); +void spdk_iscsi_init_grp_destroy(struct spdk_iscsi_init_grp *ig); +int spdk_iscsi_parse_init_grps(void); +void spdk_iscsi_init_grps_destroy(void); +void spdk_iscsi_init_grps_config_text(FILE *fp); +void spdk_iscsi_init_grps_info_json(struct spdk_json_write_ctx *w); +void spdk_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 00000000..7d96c9cb --- /dev/null +++ b/src/spdk/lib/iscsi/iscsi.c @@ -0,0 +1,4583 @@ +/*- + * 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/crc32.h" +#include "spdk/endian.h" +#include "spdk/env.h" +#include "spdk/trace.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 "iscsi/acceptor.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_spdk_iscsi = { + .mutex = PTHREAD_MUTEX_INITIALIZER, + .portal_head = TAILQ_HEAD_INITIALIZER(g_spdk_iscsi.portal_head), + .pg_head = TAILQ_HEAD_INITIALIZER(g_spdk_iscsi.pg_head), + .ig_head = TAILQ_HEAD_INITIALIZER(g_spdk_iscsi.ig_head), + .target_head = TAILQ_HEAD_INITIALIZER(g_spdk_iscsi.target_head), + .auth_group_head = TAILQ_HEAD_INITIALIZER(g_spdk_iscsi.auth_group_head), +}; + +/* random value generation */ +static void spdk_gen_random(uint8_t *buf, size_t len); +#ifndef HAVE_SRANDOMDEV +static void srandomdev(void); +#endif /* HAVE_SRANDOMDEV */ +#ifndef HAVE_ARC4RANDOM +//static uint32_t arc4random(void); +#endif /* HAVE_ARC4RANDOM */ + +/* convert from/to bin/hex */ +static int spdk_bin2hex(char *buf, size_t len, const uint8_t *data, size_t data_len); +static int spdk_hex2bin(uint8_t *data, size_t data_len, const char *str); + +static int spdk_add_transfer_task(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task); + +static int spdk_iscsi_send_r2t(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task, int offset, + int len, uint32_t transfer_tag, uint32_t *R2TSN); +static int spdk_iscsi_send_r2t_recovery(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *r2t_task, uint32_t r2t_sn, + bool send_new_r2tsn); + +static int spdk_create_iscsi_sess(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_tgt_node *target, enum session_type session_type); +static int spdk_append_iscsi_sess(struct spdk_iscsi_conn *conn, + const char *initiator_port_name, uint16_t tsih, uint16_t cid); + +static void spdk_remove_acked_pdu(struct spdk_iscsi_conn *conn, uint32_t ExpStatSN); + +static int spdk_iscsi_reject(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu, + int reason); + +#define DMIN32(A,B) ((uint32_t) ((uint32_t)(A) > (uint32_t)(B) ? (uint32_t)(B) : (uint32_t)(A))) +#define DMIN64(A,B) ((uint64_t) ((A) > (B) ? (B) : (A))) + +#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)) + +#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))) + +#if 0 +static int +spdk_match_digest_word(const uint8_t *buf, uint32_t crc32c) +{ + uint32_t l; + + l = (buf[0] & 0xffU) << 0; + l |= (buf[1] & 0xffU) << 8; + l |= (buf[2] & 0xffU) << 16; + l |= (buf[3] & 0xffU) << 24; + return (l == crc32c); +} + +static uint8_t * +spdk_make_digest_word(uint8_t *buf, size_t len, uint32_t crc32c) +{ + if (len < ISCSI_DIGEST_LEN) { + return NULL; + } + + buf[0] = (crc32c >> 0) & 0xffU; + buf[1] = (crc32c >> 8) & 0xffU; + buf[2] = (crc32c >> 16) & 0xffU; + buf[3] = (crc32c >> 24) & 0xffU; + return buf; +} +#endif + +#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 spdk_arc4random_initialized = 0; + +static uint32_t +arc4random(void) +{ + uint32_t r; + uint32_t r1, r2; + + if (!spdk_arc4random_initialized) { + srandomdev(); + spdk_arc4random_initialized = 1; + } + r1 = (uint32_t)(random() & 0xffff); + r2 = (uint32_t)(random() & 0xffff); + r = (r1 << 16) | r2; + return r; +} +#endif /* HAVE_ARC4RANDOM */ + +static void +spdk_gen_random(uint8_t *buf, size_t len) +{ +#ifdef USE_RANDOM + long l; + size_t idx; + + srandomdev(); + for (idx = 0; idx < len; idx++) { + l = random(); + buf[idx] = (uint8_t) l; + } +#else + uint32_t r; + size_t idx; + + for (idx = 0; idx < len; idx++) { + r = arc4random(); + buf[idx] = (uint8_t) r; + } +#endif /* USE_RANDOM */ +} + +static uint64_t +spdk_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 +spdk_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 +spdk_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 +spdk_islun2lun(uint64_t islun) +{ + uint64_t fmt_lun; + uint64_t method; + int lun_i; + + fmt_lun = islun; + method = (fmt_lun >> 62) & 0x03U; + fmt_lun = fmt_lun >> 48; + if (method == 0x00U) { + lun_i = (int)(fmt_lun & 0x00ffU); + } else if (method == 0x01U) { + lun_i = (int)(fmt_lun & 0x3fffU); + } else { + lun_i = 0xffffU; + } + return lun_i; +} + +static uint32_t +spdk_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; +} + +static uint32_t +spdk_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; + + crc32c = SPDK_CRC32C_INITIAL; + crc32c = spdk_crc32c_update(pdu->data, data_len, crc32c); + + 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; +} + +int +spdk_iscsi_read_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu **_pdu) +{ + struct spdk_iscsi_pdu *pdu; + struct spdk_mempool *pool; + uint32_t crc32c; + int ahs_len; + int data_len; + int max_segment_len; + int rc; + + if (conn->pdu_in_progress == NULL) { + conn->pdu_in_progress = spdk_get_pdu(); + if (conn->pdu_in_progress == NULL) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + } + + pdu = conn->pdu_in_progress; + + if (pdu->bhs_valid_bytes < ISCSI_BHS_LEN) { + rc = spdk_iscsi_conn_read_data(conn, + ISCSI_BHS_LEN - pdu->bhs_valid_bytes, + (uint8_t *)&pdu->bhs + pdu->bhs_valid_bytes); + if (rc < 0) { + *_pdu = NULL; + spdk_put_pdu(pdu); + conn->pdu_in_progress = NULL; + return rc; + } + pdu->bhs_valid_bytes += rc; + if (pdu->bhs_valid_bytes < ISCSI_BHS_LEN) { + *_pdu = NULL; + return SPDK_SUCCESS; + } + } + + data_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 = spdk_iscsi_conn_read_data(conn, + ahs_len - pdu->ahs_valid_bytes, + pdu->ahs + pdu->ahs_valid_bytes); + if (rc < 0) { + *_pdu = NULL; + spdk_put_pdu(pdu); + conn->pdu_in_progress = NULL; + return rc; + } + + pdu->ahs_valid_bytes += rc; + if (pdu->ahs_valid_bytes < ahs_len) { + *_pdu = NULL; + return SPDK_SUCCESS; + } + } + + /* Header Digest */ + if (conn->header_digest && + pdu->hdigest_valid_bytes < ISCSI_DIGEST_LEN) { + rc = spdk_iscsi_conn_read_data(conn, + ISCSI_DIGEST_LEN - pdu->hdigest_valid_bytes, + pdu->header_digest + pdu->hdigest_valid_bytes); + if (rc < 0) { + *_pdu = NULL; + spdk_put_pdu(pdu); + conn->pdu_in_progress = NULL; + return rc; + } + + pdu->hdigest_valid_bytes += rc; + if (pdu->hdigest_valid_bytes < ISCSI_DIGEST_LEN) { + *_pdu = NULL; + return SPDK_SUCCESS; + } + } + + /* copy the actual data into local buffer */ + if (pdu->data_valid_bytes < data_len) { + if (pdu->data_buf == NULL) { + if (data_len <= spdk_get_immediate_data_buffer_size()) { + pool = g_spdk_iscsi.pdu_immediate_data_pool; + } else if (data_len <= spdk_get_data_out_buffer_size()) { + pool = g_spdk_iscsi.pdu_data_out_pool; + } else { + SPDK_ERRLOG("Data(%d) > MaxSegment(%d)\n", + data_len, spdk_get_data_out_buffer_size()); + *_pdu = NULL; + spdk_put_pdu(pdu); + conn->pdu_in_progress = NULL; + return SPDK_ISCSI_CONNECTION_FATAL; + } + pdu->mobj = spdk_mempool_get(pool); + if (pdu->mobj == NULL) { + *_pdu = NULL; + return SPDK_SUCCESS; + } + pdu->data_buf = pdu->mobj->buf; + } + + rc = spdk_iscsi_conn_read_data(conn, + data_len - pdu->data_valid_bytes, + pdu->data_buf + pdu->data_valid_bytes); + if (rc < 0) { + *_pdu = NULL; + spdk_put_pdu(pdu); + conn->pdu_in_progress = NULL; + return rc; + } + + pdu->data_valid_bytes += rc; + if (pdu->data_valid_bytes < data_len) { + *_pdu = NULL; + return SPDK_SUCCESS; + } + } + + /* copy out the data digest */ + if (conn->data_digest && data_len != 0 && + pdu->ddigest_valid_bytes < ISCSI_DIGEST_LEN) { + rc = spdk_iscsi_conn_read_data(conn, + ISCSI_DIGEST_LEN - pdu->ddigest_valid_bytes, + pdu->data_digest + pdu->ddigest_valid_bytes); + if (rc < 0) { + *_pdu = NULL; + spdk_put_pdu(pdu); + conn->pdu_in_progress = NULL; + return rc; + } + + pdu->ddigest_valid_bytes += rc; + if (pdu->ddigest_valid_bytes < ISCSI_DIGEST_LEN) { + *_pdu = NULL; + return SPDK_SUCCESS; + } + } + + /* All data for this PDU has now been read from the socket. */ + conn->pdu_in_progress = NULL; + + spdk_trace_record(TRACE_ISCSI_READ_PDU, conn->id, pdu->data_valid_bytes, + (uintptr_t)pdu, pdu->bhs.opcode); + + /* Data Segment */ + if (data_len != 0) { + /* + * Determine the maximum segment length expected for this PDU. + * This will be used to make sure the initiator did not send + * us too much immediate data. + * + * This value is specified separately by the initiator and target, + * and not negotiated. So we can use the #define safely here, + * since the value is not dependent on the initiator's maximum + * segment lengths (FirstBurstLength/MaxRecvDataSegmentLength), + * and SPDK currently does not allow configuration of these values + * at runtime. + */ + if (conn->sess == NULL) { + /* + * If the connection does not yet have a session, then + * login is not complete and we use the 8KB default + * FirstBurstLength as our maximum data segment length + * value. + */ + max_segment_len = DEFAULT_FIRSTBURSTLENGTH; + } else if (pdu->bhs.opcode == ISCSI_OP_SCSI_DATAOUT) { + max_segment_len = spdk_get_data_out_buffer_size(); + } else if (pdu->bhs.opcode == ISCSI_OP_NOPOUT) { + max_segment_len = SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; + } else { + max_segment_len = spdk_get_immediate_data_buffer_size(); + } + if (data_len > max_segment_len) { + SPDK_ERRLOG("Data(%d) > MaxSegment(%d)\n", data_len, max_segment_len); + rc = spdk_iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + spdk_put_pdu(pdu); + + /* + * If spdk_iscsi_reject() was not able to reject the PDU, + * treat it as a fatal connection error. Otherwise, + * return SUCCESS here so that the caller will continue + * to attempt to read PDUs. + */ + rc = (rc < 0) ? SPDK_ISCSI_CONNECTION_FATAL : SPDK_SUCCESS; + return rc; + } + + pdu->data = pdu->data_buf; + pdu->data_from_mempool = true; + pdu->data_segment_len = data_len; + } + + /* check digest */ + if (conn->header_digest) { + crc32c = spdk_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); + spdk_put_pdu(pdu); + return SPDK_ISCSI_CONNECTION_FATAL; + } + } + if (conn->data_digest && data_len != 0) { + crc32c = spdk_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); + spdk_put_pdu(pdu); + return SPDK_ISCSI_CONNECTION_FATAL; + } + } + + *_pdu = pdu; + return 1; +} + +int +spdk_iscsi_build_iovecs(struct spdk_iscsi_conn *conn, struct iovec *iovec, + struct spdk_iscsi_pdu *pdu) +{ + int iovec_cnt = 0; + uint32_t crc32c; + int enable_digest; + int total_ahs_len; + int data_len; + + total_ahs_len = pdu->bhs.total_ahs_len; + data_len = DGET24(pdu->bhs.data_segment_len); + + enable_digest = 1; + if (pdu->bhs.opcode == ISCSI_OP_LOGIN_RSP) { + /* this PDU should be sent without digest */ + enable_digest = 0; + } + + /* BHS */ + iovec[iovec_cnt].iov_base = &pdu->bhs; + iovec[iovec_cnt].iov_len = ISCSI_BHS_LEN; + iovec_cnt++; + + /* AHS */ + if (total_ahs_len > 0) { + iovec[iovec_cnt].iov_base = pdu->ahs; + iovec[iovec_cnt].iov_len = 4 * total_ahs_len; + iovec_cnt++; + } + + /* Header Digest */ + if (enable_digest && conn->header_digest) { + crc32c = spdk_iscsi_pdu_calc_header_digest(pdu); + MAKE_DIGEST_WORD(pdu->header_digest, crc32c); + + iovec[iovec_cnt].iov_base = pdu->header_digest; + iovec[iovec_cnt].iov_len = ISCSI_DIGEST_LEN; + iovec_cnt++; + } + + /* Data Segment */ + if (data_len > 0) { + iovec[iovec_cnt].iov_base = pdu->data; + iovec[iovec_cnt].iov_len = ISCSI_ALIGN(data_len); + iovec_cnt++; + } + + /* Data Digest */ + if (enable_digest && conn->data_digest && data_len != 0) { + crc32c = spdk_iscsi_pdu_calc_data_digest(pdu); + MAKE_DIGEST_WORD(pdu->data_digest, crc32c); + + iovec[iovec_cnt].iov_base = pdu->data_digest; + iovec[iovec_cnt].iov_len = ISCSI_DIGEST_LEN; + iovec_cnt++; + } + + return iovec_cnt; +} + +static int +spdk_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 +spdk_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 = spdk_iscsi_param_find(conn->params, key); + if (param == NULL) { + param = spdk_iscsi_param_find(conn->sess->params, key); + if (param == NULL) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "no key %.64s\n", key); + return data_len; + } + } + rc = spdk_iscsi_append_text(conn, param->key, param->val, data, + alloc_len, data_len); + return rc; +} + +static int +spdk_iscsi_get_authinfo(struct spdk_iscsi_conn *conn, const char *authuser) +{ + int ag_tag; + int rc; + + if (conn->sess->target != NULL) { + ag_tag = conn->sess->target->chap_group; + } else { + ag_tag = -1; + } + if (ag_tag < 0) { + ag_tag = g_spdk_iscsi.chap_group; + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "ag_tag=%d\n", ag_tag); + + rc = spdk_iscsi_chap_get_authinfo(&conn->auth, authuser, ag_tag); + if (rc < 0) { + SPDK_ERRLOG("chap_get_authinfo() failed\n"); + return -1; + } + return 0; +} + +static int +spdk_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 *val; + const char *user; + const char *response; + 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 ((val = spdk_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", val); + 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; + spdk_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 = spdk_iscsi_append_text(conn, "CHAP_A", new_val, + data, alloc_len, total); + + /* Identifier is one octet */ + spdk_gen_random(conn->auth.chap_id, 1); + snprintf(in_val, ISCSI_TEXT_MAX_VAL_LEN, "%d", + (int) conn->auth.chap_id[0]); + total = spdk_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; + spdk_gen_random(conn->auth.chap_challenge, + conn->auth.chap_challenge_len); + spdk_bin2hex(in_val, ISCSI_TEXT_MAX_VAL_LEN, + conn->auth.chap_challenge, + conn->auth.chap_challenge_len); + total = spdk_iscsi_append_text(conn, "CHAP_C", in_val, + data, alloc_len, total); + + conn->auth.chap_phase = ISCSI_CHAP_PHASE_WAIT_NR; + } else if ((val = spdk_iscsi_param_get_val(params, "CHAP_N")) != NULL) { + uint8_t resmd5[SPDK_MD5DIGEST_LEN]; + uint8_t tgtmd5[SPDK_MD5DIGEST_LEN]; + struct spdk_md5ctx md5ctx; + + user = val; + if (conn->auth.chap_phase != ISCSI_CHAP_PHASE_WAIT_NR) { + SPDK_ERRLOG("CHAP sequence error\n"); + goto error_return; + } + + response = spdk_iscsi_param_get_val(params, "CHAP_R"); + if (response == NULL) { + SPDK_ERRLOG("no response\n"); + goto error_return; + } + rc = spdk_hex2bin(resmd5, SPDK_MD5DIGEST_LEN, response); + if (rc < 0 || rc != SPDK_MD5DIGEST_LEN) { + SPDK_ERRLOG("response format error\n"); + goto error_return; + } + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "got CHAP_N/CHAP_R\n"); + + rc = spdk_iscsi_get_authinfo(conn, val); + if (rc < 0) { + //SPDK_ERRLOG("auth user or secret is missing\n"); + SPDK_ERRLOG("iscsi_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 (user %.64s)\n", user); + goto error_return; + } + + spdk_md5init(&md5ctx); + /* Identifier */ + spdk_md5update(&md5ctx, conn->auth.chap_id, 1); + /* followed by secret */ + spdk_md5update(&md5ctx, conn->auth.secret, + strlen(conn->auth.secret)); + /* followed by Challenge Value */ + spdk_md5update(&md5ctx, conn->auth.chap_challenge, + conn->auth.chap_challenge_len); + /* tgtmd5 is expecting Response Value */ + spdk_md5final(tgtmd5, &md5ctx); + + spdk_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 (user %.64s)\n", user); + goto error_return; + } + /* OK initiator's secret */ + conn->authenticated = 1; + + /* mutual CHAP? */ + val = spdk_iscsi_param_get_val(params, "CHAP_I"); + if (val != NULL) { + conn->auth.chap_mid[0] = (uint8_t) strtol(val, NULL, 10); + challenge = spdk_iscsi_param_get_val(params, "CHAP_C"); + if (challenge == NULL) { + SPDK_ERRLOG("CHAP sequence error\n"); + goto error_return; + } + rc = spdk_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; +#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 (user %.64s)\n", user); + goto error_return; + } + + spdk_md5init(&md5ctx); + /* Identifier */ + spdk_md5update(&md5ctx, conn->auth.chap_mid, 1); + /* followed by secret */ + spdk_md5update(&md5ctx, conn->auth.msecret, + strlen(conn->auth.msecret)); + /* followed by Challenge Value */ + spdk_md5update(&md5ctx, conn->auth.chap_mchallenge, + conn->auth.chap_mchallenge_len); + /* tgtmd5 is Response Value */ + spdk_md5final(tgtmd5, &md5ctx); + + spdk_bin2hex(in_val, ISCSI_TEXT_MAX_VAL_LEN, + tgtmd5, SPDK_MD5DIGEST_LEN); + + total = spdk_iscsi_append_text(conn, "CHAP_N", + conn->auth.muser, data, alloc_len, total); + total = spdk_iscsi_append_text(conn, "CHAP_R", + in_val, data, alloc_len, total); + } else { + /* not mutual */ + if (conn->req_mutual) { + 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 +spdk_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; + + 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) { + memcpy(data + data_len, pdu->ahs, (4 * total_ahs_len)); + data_len += (4 * total_ahs_len); + } + + if (conn->header_digest) { + memcpy(data + data_len, pdu->header_digest, ISCSI_DIGEST_LEN); + data_len += ISCSI_DIGEST_LEN; + } + + rsp_pdu = spdk_get_pdu(); + 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_TRACEDUMP(SPDK_LOG_ISCSI, "PDU", (void *)&rsp_pdu->bhs, ISCSI_BHS_LEN); + + spdk_iscsi_conn_write_pdu(conn, rsp_pdu); + + return 0; +} + +static int +spdk_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_spdk_iscsi.FirstBurstLength) { + SPDK_ERRLOG("FirstBurstLength(%d) > iSCSI target restriction(%d)\n", + conn->sess->FirstBurstLength, g_spdk_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; +} + +/* + * The response function of spdk_iscsi_op_login + * return: + * 0:success; + * -1:error; + */ +static int +spdk_iscsi_op_login_response(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, struct iscsi_param *params) +{ + struct iscsi_bhs_login_rsp *rsph; + int rc; + + 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_TRACEDUMP(SPDK_LOG_ISCSI, "PDU", (uint8_t *)rsph, ISCSI_BHS_LEN); + SPDK_TRACEDUMP(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; + } + spdk_iscsi_conn_write_pdu(conn, rsp_pdu); + + /* after send PDU digest on/off */ + if (conn->full_feature) { + /* update internal variables */ + rc = spdk_iscsi_copy_param2var(conn); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_copy_param2var() failed\n"); + spdk_iscsi_param_free(params); + return -1; + } + /* check value */ + rc = spdk_iscsi_check_values(conn); + if (rc < 0) { + SPDK_ERRLOG("iscsi_check_values() failed\n"); + spdk_iscsi_param_free(params); + return -1; + } + } + + spdk_iscsi_param_free(params); + return 0; +} + +/* + * This function is used to del the original param and update it with new + * value + * return: + * 0: success + * otherwise: error + */ +static int +spdk_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 = spdk_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 = spdk_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 = spdk_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 = spdk_iscsi_param_find(conn->params, key); + if (new_param == NULL) { + SPDK_ERRLOG("spdk_iscsi_param_find() failed\n"); + return SPDK_ISCSI_LOGIN_ERROR_PARAMETER; + } + new_param->state_index = index; + return rc; +} + +/* + * The function which is used to handle the part of session discovery + * return: + * 0, success; + * otherwise: error; + */ +static int +spdk_iscsi_op_login_session_discovery_chap(struct spdk_iscsi_conn *conn) +{ + int rc = 0; + + if (g_spdk_iscsi.disable_chap) { + conn->req_auth = 0; + rc = spdk_iscsi_op_login_update_param(conn, "AuthMethod", "None", "None"); + if (rc < 0) { + return rc; + } + } else if (g_spdk_iscsi.require_chap) { + conn->req_auth = 1; + rc = spdk_iscsi_op_login_update_param(conn, "AuthMethod", "CHAP", "CHAP"); + if (rc < 0) { + return rc; + } + } + if (g_spdk_iscsi.mutual_chap) { + conn->req_mutual = 1; + } + + return rc; +} + +/* + * This function is used to update the param related with chap + * return: + * 0: success + * otherwise: error + */ +static int +spdk_iscsi_op_login_negotiate_chap_param(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + struct spdk_iscsi_tgt_node *target) +{ + int rc; + + if (target->disable_chap) { + conn->req_auth = 0; + rc = spdk_iscsi_op_login_update_param(conn, "AuthMethod", "None", "None"); + if (rc < 0) { + return rc; + } + } else if (target->require_chap) { + conn->req_auth = 1; + rc = spdk_iscsi_op_login_update_param(conn, "AuthMethod", "CHAP", "CHAP"); + if (rc < 0) { + return rc; + } + } + + if (target->mutual_chap) { + conn->req_mutual = 1; + } + + 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 = spdk_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 = spdk_iscsi_op_login_update_param(conn, "DataDigest", "CRC32C", "CRC32C"); + if (rc < 0) { + return rc; + } + } + + return 0; +} + +/* + * This function use to check the session + * return: + * 0, success + * otherwise: error + */ +static int +spdk_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", + spdk_iscsi_get_isid(rsph->isid), from_be16(&rsph->tsih), cid); + if (rsph->tsih != 0) { + /* multiple connections */ + rc = spdk_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", + spdk_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 = ISCSI_LOGIN_CONN_ADD_FAIL; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + } else if (!g_spdk_iscsi.AllowDuplicateIsid) { + /* new session, drop old sess by the initiator */ + spdk_iscsi_drop_conns(conn, initiator_port_name, 0 /* drop old */); + } + + return rc; +} + +/* + * This function is used to check the target info + * return: + * 0: success + * otherwise: error + */ +static int +spdk_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 = spdk_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; + } + result = spdk_iscsi_tgt_node_access(conn, *target, + conn->initiator_name, + conn->initiator_addr); + if (!result) { + SPDK_ERRLOG("access denied\n"); + /* Not found */ + rsph->status_class = ISCSI_CLASS_INITIATOR_ERROR; + rsph->status_detail = ISCSI_LOGIN_TARGET_NOT_FOUND; + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + + 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 +spdk_iscsi_op_login_session_normal(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + char *initiator_port_name, + struct iscsi_param *params, + struct spdk_iscsi_tgt_node **target, + int cid) +{ + 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 = spdk_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); + return rc; + } + snprintf(conn->target_short_name, MAX_TARGET_NAME, "%s", + target_short_name); + } + + pthread_mutex_lock(&g_spdk_iscsi.mutex); + rc = spdk_iscsi_op_login_check_target(conn, rsp_pdu, target_name, target); + pthread_mutex_unlock(&g_spdk_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->portal->group->tag); + + rc = spdk_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 = spdk_iscsi_op_login_negotiate_chap_param(conn, rsp_pdu, *target); + pthread_mutex_unlock(&((*target)->mutex)); + + return rc; +} + +/* + * This function is used to judge the session type + * return + * 0: success + * otherwise, error + */ +static int +spdk_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 = spdk_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 initialize the port info + * return + * 0: success + * otherwise: error + */ +static int +spdk_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 = spdk_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, spdk_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 set the info in the connection data structure + * return + * 0: success + * otherwise: error + */ +static int +spdk_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, + struct spdk_iscsi_tgt_node *target, int cid) +{ + int rc = 0; + struct iscsi_bhs_login_rsp *rsph; + + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + conn->authenticated = 0; + conn->auth.chap_phase = ISCSI_CHAP_PHASE_WAIT_A; + conn->cid = cid; + + if (conn->sess == NULL) { + /* new session */ + rc = spdk_create_iscsi_sess(conn, target, session_type); + if (rc < 0) { + 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->StatSN = from_be32(&rsph->stat_sn); + conn->sess->initiator_port = spdk_scsi_port_create(spdk_iscsi_get_isid(rsph->isid), + 0, initiator_port_name); + conn->sess->isid = spdk_iscsi_get_isid(rsph->isid); + conn->sess->target = target; + + /* 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 +spdk_iscsi_op_login_set_target_info(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + enum session_type session_type, + int alloc_len, + struct spdk_iscsi_tgt_node *target) +{ + char buf[MAX_TMPBUF]; + const char *val; + int rc = 0; + struct spdk_iscsi_portal *portal = conn->portal; + + /* declarative parameters */ + if (target != NULL) { + pthread_mutex_lock(&target->mutex); + if (target->alias != NULL) { + snprintf(buf, sizeof buf, "%s", target->alias); + } else { + snprintf(buf, sizeof buf, "%s", ""); + } + pthread_mutex_unlock(&target->mutex); + rc = spdk_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", portal->host, portal->port, + portal->group->tag); + rc = spdk_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", portal->group->tag); + rc = spdk_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 = spdk_iscsi_param_get_val(conn->sess->params, "TargetAlias"); + if (val != NULL && strlen(val) != 0) { + rsp_pdu->data_segment_len = spdk_iscsi_append_param(conn, + "TargetAlias", + rsp_pdu->data, + alloc_len, + rsp_pdu->data_segment_len); + } + if (session_type == SESSION_TYPE_DISCOVERY) { + rsp_pdu->data_segment_len = spdk_iscsi_append_param(conn, + "TargetAddress", + rsp_pdu->data, + alloc_len, + rsp_pdu->data_segment_len); + } + rsp_pdu->data_segment_len = spdk_iscsi_append_param(conn, + "TargetPortalGroupTag", + rsp_pdu->data, + alloc_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 +spdk_iscsi_op_login_phase_none(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + struct iscsi_param *params, + int alloc_len, int cid) +{ + enum session_type session_type; + char initiator_port_name[MAX_INITIATOR_NAME]; + struct iscsi_bhs_login_rsp *rsph; + struct spdk_iscsi_tgt_node *target = NULL; + int rc = 0; + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + + conn->target = NULL; + conn->dev = NULL; + + rc = spdk_iscsi_op_login_initialize_port(conn, rsp_pdu, + initiator_port_name, MAX_INITIATOR_NAME, params); + if (rc < 0) { + return rc; + } + + rc = spdk_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 = spdk_iscsi_op_login_session_normal(conn, rsp_pdu, + initiator_port_name, + params, &target, cid); + if (rc < 0) { + return rc; + } + + } else if (session_type == SESSION_TYPE_DISCOVERY) { + target = NULL; + rsph->tsih = 0; + + /* force target flags */ + pthread_mutex_lock(&g_spdk_iscsi.mutex); + rc = spdk_iscsi_op_login_session_discovery_chap(conn); + pthread_mutex_unlock(&g_spdk_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 = spdk_iscsi_op_login_set_conn_info(conn, rsp_pdu, initiator_port_name, + session_type, target, cid); + if (rc < 0) { + return rc; + } + + /* limit conns on discovery session */ + if (session_type == SESSION_TYPE_DISCOVERY) { + conn->sess->MaxConnections = 1; + rc = spdk_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; + } + } + + rc = spdk_iscsi_op_login_set_target_info(conn, rsp_pdu, session_type, + alloc_len, target); + if (rc < 0) { + return rc; + } + + return rc; +} + +/* + * The function which is used to initialize the internal response data + * structure of iscsi login function. + * return: + * 0, success; + * otherwise, error; + */ +static int +spdk_iscsi_op_login_rsp_init(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *pdu, struct spdk_iscsi_pdu *rsp_pdu, + struct iscsi_param **params, int *alloc_len, int *cid) +{ + + struct iscsi_bhs_login_req *reqh; + struct iscsi_bhs_login_rsp *rsph; + int rc; + + 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; + + /* Default MaxRecvDataSegmentLength - RFC3720(12.12) */ + if (conn->MaxRecvDataSegmentLength < 8192) { + *alloc_len = 8192; + } else { + *alloc_len = conn->MaxRecvDataSegmentLength; + } + + rsp_pdu->data = calloc(1, *alloc_len); + if (!rsp_pdu->data) { + SPDK_ERRLOG("calloc() failed for data segment\n"); + return -ENOMEM; + } + + 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); + *cid = from_be16(&reqh->cid); + + if (rsph->tsih) { + rsph->stat_sn = reqh->exp_stat_sn; + } + + SPDK_TRACEDUMP(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"); + return SPDK_ISCSI_LOGIN_ERROR_RESPONSE; + } + /* make sure reqh->version_max < ISCSI_VERSION */ + if (reqh->version_min > ISCSI_VERSION) { + SPDK_ERRLOG("unsupported version %d/%d\n", reqh->version_min, + reqh->version_max); + /* 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; + } + + /* store incoming parameters */ + rc = spdk_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"); + 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 set the csg bit case in rsp + * return: + * 0, success + * otherwise: error + */ +static int +spdk_iscsi_op_login_rsp_handle_csg_bit(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, + struct iscsi_param *params, int alloc_len) +{ + 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 = spdk_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 = 1; + } else { + rc = spdk_iscsi_auth_params(conn, params, auth_method, + rsp_pdu->data, alloc_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 == 0) { + /* 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_TRACEDUMP(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->req_auth) { + /* 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 = 1; + } + } + if (conn->authenticated == 0) { + 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 +spdk_iscsi_op_login_notify_session_info(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu) +{ + struct spdk_iscsi_portal *portal = conn->portal; + 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_NOTICELOG("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, + portal->host, portal->port, portal->group->tag, + conn->sess->isid, conn->sess->tsih, conn->cid, + (spdk_iscsi_param_eq_val(conn->params, "HeaderDigest", "CRC32C") + ? "on" : "off"), + (spdk_iscsi_param_eq_val(conn->params, "DataDigest", "CRC32C") + ? "on" : "off")); + } else if (conn->sess->session_type == SESSION_TYPE_DISCOVERY) { + /* discovery session */ + SPDK_NOTICELOG("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, + portal->host, portal->port, portal->group->tag, + conn->sess->isid, conn->sess->tsih, conn->cid, + (spdk_iscsi_param_eq_val(conn->params, "HeaderDigest", "CRC32C") + ? "on" : "off"), + (spdk_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 +spdk_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 = spdk_iscsi_op_login_notify_session_info(conn, rsp_pdu); + if (rc < 0) { + return rc; + } + + conn->full_feature = 1; + spdk_iscsi_conn_migration(conn); + 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 +spdk_iscsi_op_login_rsp_handle(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_pdu *rsp_pdu, struct iscsi_param **params, + int alloc_len) +{ + int rc; + struct iscsi_bhs_login_rsp *rsph; + rsph = (struct iscsi_bhs_login_rsp *)&rsp_pdu->bhs; + + /* negotiate parameters */ + rc = spdk_iscsi_negotiate_params(conn, params, rsp_pdu->data, alloc_len, + rsp_pdu->data_segment_len); + if (rc < 0) { + /* + * spdk_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_TRACEDUMP(SPDK_LOG_ISCSI, "Negotiated Params", rsp_pdu->data, rc); + + /* handle the CSG bit case */ + rc = spdk_iscsi_op_login_rsp_handle_csg_bit(conn, rsp_pdu, *params, + alloc_len); + if (rc < 0) { + return rc; + } + + /* handle the T bit case */ + if (ISCSI_BHS_LOGIN_GET_TBIT(rsph->flags)) { + rc = spdk_iscsi_op_login_rsp_handle_t_bit(conn, rsp_pdu); + } + + return rc; +} + +static int +spdk_iscsi_op_login(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + int rc; + struct spdk_iscsi_pdu *rsp_pdu; + struct iscsi_param *params = NULL; + struct iscsi_param **params_p = ¶ms; + int alloc_len; + int cid; + + if (conn->full_feature && conn->sess != NULL && + conn->sess->session_type == SESSION_TYPE_DISCOVERY) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + + rsp_pdu = spdk_get_pdu(); + if (rsp_pdu == NULL) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + rc = spdk_iscsi_op_login_rsp_init(conn, pdu, rsp_pdu, params_p, + &alloc_len, &cid); + if (rc == SPDK_ISCSI_LOGIN_ERROR_RESPONSE || rc == SPDK_ISCSI_LOGIN_ERROR_PARAMETER) { + spdk_iscsi_op_login_response(conn, rsp_pdu, *params_p); + return rc; + } + + /* For other values, we need to directly return */ + if (rc < 0) { + spdk_put_pdu(rsp_pdu); + return rc; + } + + if (conn->state == ISCSI_CONN_STATE_INVALID) { + rc = spdk_iscsi_op_login_phase_none(conn, rsp_pdu, *params_p, + alloc_len, cid); + if (rc == SPDK_ISCSI_LOGIN_ERROR_RESPONSE || rc == SPDK_ISCSI_LOGIN_ERROR_PARAMETER) { + spdk_iscsi_op_login_response(conn, rsp_pdu, *params_p); + return rc; + } + } + + rc = spdk_iscsi_op_login_rsp_handle(conn, rsp_pdu, params_p, alloc_len); + if (rc == SPDK_ISCSI_LOGIN_ERROR_RESPONSE) { + spdk_iscsi_op_login_response(conn, rsp_pdu, *params_p); + return rc; + } + + rc = spdk_iscsi_op_login_response(conn, rsp_pdu, *params_p); + if (rc == 0) { + conn->state = ISCSI_CONN_STATE_RUNNING; + } else { + SPDK_ERRLOG("login error - connection will be destroyed\n"); + } + + return rc; +} + +static int +spdk_iscsi_op_text(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + struct iscsi_param *params = NULL; + struct iscsi_param **params_p = ¶ms; + struct spdk_iscsi_pdu *rsp_pdu; + uint8_t *data; + uint64_t lun; + uint32_t task_tag; + uint32_t CmdSN; + uint32_t ExpStatSN; + 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); + CmdSN = from_be32(&reqh->cmd_sn); + pdu->cmd_sn = CmdSN; + 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", + CmdSN, 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 spdk_iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + } + + /* store incoming parameters */ + rc = spdk_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"); + spdk_iscsi_param_free(params); + return -1; + } + + data = calloc(1, alloc_len); + if (!data) { + SPDK_ERRLOG("calloc() failed for data segment\n"); + spdk_iscsi_param_free(params); + return -ENOMEM; + } + + /* negotiate parameters */ + data_len = spdk_iscsi_negotiate_params(conn, params_p, + data, alloc_len, data_len); + if (data_len < 0) { + SPDK_ERRLOG("spdk_iscsi_negotiate_params() failed\n"); + spdk_iscsi_param_free(*params_p); + free(data); + return -1; + } + + /* sendtargets is special case */ + val = spdk_iscsi_param_get_val(*params_p, "SendTargets"); + if (val != NULL) { + if (spdk_iscsi_param_eq_val(conn->sess->params, + "SessionType", "Discovery")) { + if (strcasecmp(val, "") == 0) { + val = "ALL"; + } + + data_len = spdk_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 = spdk_iscsi_append_text(conn, + "SendTargets", + "Reject", data, + alloc_len, + data_len); + } else { + data_len = spdk_iscsi_send_tgts(conn, + conn->initiator_name, + conn->initiator_addr, + val, data, alloc_len, + data_len); + } + } + } else { + if (spdk_iscsi_param_eq_val(conn->sess->params, "SessionType", "Discovery")) { + spdk_iscsi_param_free(*params_p); + free(data); + return SPDK_ISCSI_CONNECTION_FATAL; + } + } + + SPDK_TRACEDUMP(SPDK_LOG_ISCSI, "Negotiated Params", data, data_len); + + /* response PDU */ + rsp_pdu = spdk_get_pdu(); + if (rsp_pdu == NULL) { + spdk_iscsi_param_free(*params_p); + 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); + + spdk_iscsi_conn_write_pdu(conn, rsp_pdu); + + /* update internal variables */ + rc = spdk_iscsi_copy_param2var(conn); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_copy_param2var() failed\n"); + spdk_iscsi_param_free(*params_p); + return -1; + } + + /* check value */ + rc = spdk_iscsi_check_values(conn); + if (rc < 0) { + SPDK_ERRLOG("iscsi_check_values() failed\n"); + spdk_iscsi_param_free(*params_p); + return -1; + } + + spdk_iscsi_param_free(*params_p); + return 0; +} + +static int +spdk_iscsi_op_logout(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + char buf[MAX_TMPBUF]; + struct spdk_iscsi_pdu *rsp_pdu; + uint32_t task_tag; + uint32_t CmdSN; + 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); + CmdSN = from_be32(&reqh->cmd_sn); + pdu->cmd_sn = CmdSN; + 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 (reqh->reason != 0 && conn->sess->session_type == SESSION_TYPE_DISCOVERY) { + SPDK_ERRLOG("only logout with close the session reason can be in discovery session"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + if (conn->sess != NULL) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "CmdSN=%u, ExpStatSN=%u, StatSN=%u, ExpCmdSN=%u, MaxCmdSN=%u\n", + CmdSN, ExpStatSN, conn->StatSN, + conn->sess->ExpCmdSN, conn->sess->MaxCmdSN); + + if (CmdSN != conn->sess->ExpCmdSN) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "CmdSN(%u) might have dropped\n", CmdSN); + /* ignore error */ + } + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "CmdSN=%u, ExpStatSN=%u, StatSN=%u\n", + CmdSN, 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) { + response = 0; // connection or session closed successfully + spdk_iscsi_conn_logout(conn); + } else { + response = 1; + } + + /* response PDU */ + rsp_pdu = spdk_get_pdu(); + 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, CmdSN); + to_be32(&rsph->max_cmd_sn, CmdSN); + } + + rsph->time_2_wait = 0; + rsph->time_2_retain = 0; + + spdk_iscsi_conn_write_pdu(conn, rsp_pdu); + + if (conn->sess == NULL) { + /* + * login failed but initiator still sent a logout rather than + * just closing the TCP connection. + */ + snprintf(buf, sizeof buf, "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 (spdk_iscsi_param_eq_val(conn->sess->params, "SessionType", "Normal")) { + snprintf(buf, sizeof buf, "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, + (spdk_iscsi_param_eq_val(conn->params, "HeaderDigest", "CRC32C") + ? "on" : "off"), + (spdk_iscsi_param_eq_val(conn->params, "DataDigest", "CRC32C") + ? "on" : "off")); + } else { + /* discovery session */ + snprintf(buf, sizeof buf, "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, + (spdk_iscsi_param_eq_val(conn->params, "HeaderDigest", "CRC32C") + ? "on" : "off"), + (spdk_iscsi_param_eq_val(conn->params, "DataDigest", "CRC32C") + ? "on" : "off")); + } + + SPDK_NOTICELOG("%s", buf); + + 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 * +spdk_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 * +spdk_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; +} + +static int +spdk_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; + + primary = spdk_iscsi_task_get_primary(task); + + /* DATA PDU */ + rsp_pdu = spdk_get_pdu(); + rsph = (struct iscsi_bhs_data_in *)&rsp_pdu->bhs; + rsp_pdu->data = task->scsi.iovs[0].iov_base + 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 && !spdk_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); + + if (F_bit && S_bit) { + to_be32(&rsph->res_cnt, residual_len); + } + + spdk_iscsi_conn_write_pdu(conn, rsp_pdu); + + return DataSN; +} + +static int +spdk_iscsi_transfer_in(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task) +{ + uint32_t DataSN; + int transfer_len; + int data_len; + int segment_len; + int offset; + int residual_len = 0; + int sent_status; + int len; + int datain_flag = 0; + int datain_seq_cnt; + int i; + int sequence_end; + struct spdk_iscsi_task *primary; + + primary = spdk_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) { + if (task != primary) { + conn->data_in_cnt--; + /* Handle the case when primary task return success but the subtask failed */ + if (primary->bytes_completed == primary->scsi.transfer_len && + primary->scsi.status == SPDK_SCSI_STATUS_GOOD) { + conn->data_in_cnt--; + } + } else { + /* handle the case that it is a primary task which has subtasks */ + if (primary->scsi.transfer_len != primary->scsi.length) { + conn->data_in_cnt--; + } + } + + 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 = DMIN32(((i + 1) * conn->sess->MaxBurstLength), + transfer_len); + + /* send data splitted by segment_len */ + for (; offset < sequence_end; offset += segment_len) { + len = DMIN32(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 = spdk_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; +} + +/* + * 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 +spdk_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, spdk_iscsi_task_get_bhs(task), ISCSI_BHS_LEN)) { + return true; + } + } + + TAILQ_FOREACH(task, &conn->queued_r2t_tasks, link) { + if (!memcmp(&pdu->bhs, spdk_iscsi_task_get_bhs(task), ISCSI_BHS_LEN)) { + return true; + } + } + + return false; +} + +static void spdk_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 void spdk_iscsi_queue_mgmt_task(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task, + enum spdk_scsi_task_func func) +{ + spdk_scsi_dev_queue_mgmt_task(conn->dev, &task->scsi, func); +} + +int spdk_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 == 0) { + task->scsi.lun = spdk_scsi_dev_get_lun(conn->dev, task->lun_id); + if (task->scsi.lun == NULL) { + TAILQ_REMOVE(&conn->queued_datain_tasks, task, link); + spdk_scsi_task_process_null_lun(&task->scsi); + spdk_iscsi_task_cpl(&task->scsi); + return 0; + } + task->current_datain_offset = task->scsi.length; + conn->data_in_cnt++; + spdk_iscsi_queue_task(conn, task); + continue; + } + 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 = spdk_iscsi_task_get(conn, task, spdk_iscsi_task_cpl); + assert(subtask != NULL); + subtask->scsi.offset = task->current_datain_offset; + subtask->scsi.length = DMIN32(SPDK_BDEV_LARGE_BUF_MAX_SIZE, remaining_size); + spdk_scsi_task_set_data(&subtask->scsi, NULL, 0); + task->current_datain_offset += subtask->scsi.length; + conn->data_in_cnt++; + + task->scsi.lun = spdk_scsi_dev_get_lun(conn->dev, task->lun_id); + if (task->scsi.lun == NULL) { + /* Remove the primary task from the list if this is the last subtask */ + if (task->current_datain_offset == task->scsi.transfer_len) { + TAILQ_REMOVE(&conn->queued_datain_tasks, task, link); + } + subtask->scsi.transfer_len = subtask->scsi.length; + spdk_scsi_task_process_null_lun(&subtask->scsi); + spdk_iscsi_task_cpl(&subtask->scsi); + return 0; + } + + spdk_iscsi_queue_task(conn, subtask); + } + if (task->current_datain_offset == task->scsi.transfer_len) { + TAILQ_REMOVE(&conn->queued_datain_tasks, task, link); + } + } + return 0; +} + +static int spdk_iscsi_op_scsi_read(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task) +{ + int32_t remaining_size; + + TAILQ_INIT(&task->subtask_list); + task->scsi.dxfer_dir = SPDK_SCSI_DIR_FROM_DEV; + task->parent = NULL; + task->scsi.offset = 0; + task->scsi.length = DMIN32(SPDK_BDEV_LARGE_BUF_MAX_SIZE, task->scsi.transfer_len); + spdk_scsi_task_set_data(&task->scsi, NULL, 0); + + remaining_size = task->scsi.transfer_len - task->scsi.length; + task->current_datain_offset = 0; + + if (remaining_size == 0) { + spdk_iscsi_queue_task(conn, task); + return 0; + } + + TAILQ_INSERT_TAIL(&conn->queued_datain_tasks, task, link); + + return spdk_iscsi_conn_handle_queued_datain_tasks(conn); +} + +static int +spdk_iscsi_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 F_bit, R_bit, W_bit; + int lun_i, rc; + 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; + + F_bit = reqh->final_bit; + 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_TRACEDUMP(SPDK_LOG_ISCSI, "CDB", cdb, 16); + + task = spdk_iscsi_task_get(conn, NULL, spdk_iscsi_task_cpl); + if (!task) { + SPDK_ERRLOG("Unable to acquire task\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + spdk_iscsi_task_associate_pdu(task, pdu); + lun_i = spdk_islun2lun(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"); + spdk_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; + + if (task->scsi.lun == NULL) { + spdk_scsi_task_process_null_lun(&task->scsi); + spdk_iscsi_task_cpl(&task->scsi); + return 0; + } + + /* no bi-directional support */ + if (R_bit) { + return spdk_iscsi_op_scsi_read(conn, task); + } else if (W_bit) { + task->scsi.dxfer_dir = SPDK_SCSI_DIR_TO_DEV; + + if ((conn->sess->ErrorRecoveryLevel >= 1) && + (spdk_iscsi_compare_pdu_bhs_within_existed_r2t_tasks(conn, pdu))) { + spdk_iscsi_task_response(conn, task); + spdk_iscsi_task_put(task); + return 0; + } + + if (pdu->data_segment_len > transfer_len) { + SPDK_ERRLOG("data segment len(=%d) > task transfer len(=%d)\n", + (int)pdu->data_segment_len, transfer_len); + spdk_iscsi_task_put(task); + rc = spdk_iscsi_reject(conn, pdu, + ISCSI_REASON_PROTOCOL_ERROR); + if (rc < 0) { + SPDK_ERRLOG("iscsi_reject() failed\n"); + } + return rc; + } + + /* 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)) { + spdk_iscsi_task_put(task); + rc = spdk_iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + if (rc < 0) { + SPDK_ERRLOG("iscsi_reject() failed\n"); + } + return rc; + } + + if (F_bit && pdu->data_segment_len < transfer_len) { + /* needs R2T */ + rc = spdk_add_transfer_task(conn, task); + if (rc < 0) { + SPDK_ERRLOG("add_transfer_task() failed\n"); + spdk_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, pdu->data_segment_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, transfer_len); + task->scsi.length = transfer_len; + } + } else { + /* neither R nor W bit set */ + task->scsi.dxfer_dir = SPDK_SCSI_DIR_NONE; + if (transfer_len > 0) { + spdk_iscsi_task_put(task); + SPDK_ERRLOG("Reject scsi cmd with EDTL > 0 but (R | W) == 0\n"); + return spdk_iscsi_reject(conn, pdu, ISCSI_REASON_INVALID_PDU_FIELD); + } + } + + spdk_iscsi_queue_task(conn, task); + return 0; +} + +void +spdk_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 = spdk_get_pdu(); + 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: + rsph->response = ISCSI_TASK_FUNC_RESP_COMPLETE; + break; + case SPDK_SCSI_TASK_MGMT_RESP_SUCCESS: + 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); + + spdk_iscsi_conn_write_pdu(conn, rsp_pdu); +} + +void spdk_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 = spdk_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 (spdk_iscsi_task_is_read(primary)) { + rc = spdk_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 = spdk_get_pdu(); + 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 (!spdk_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); + + spdk_iscsi_conn_write_pdu(conn, rsp_pdu); +} + +static struct spdk_iscsi_task * +spdk_get_transfer_task(struct spdk_iscsi_conn *conn, uint32_t transfer_tag) +{ + int i; + + for (i = 0; i < conn->pending_r2t; i++) { + if (conn->outstanding_r2t_tasks[i]->ttt == transfer_tag) { + return (conn->outstanding_r2t_tasks[i]); + } + } + + return NULL; +} + +static int +spdk_iscsi_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_islun2lun(lun); + dev = conn->dev; + + task = spdk_iscsi_task_get(conn, NULL, spdk_iscsi_task_mgmt_cpl); + if (!task) { + SPDK_ERRLOG("Unable to acquire task\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + spdk_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); + + switch (function) { + /* abort task identified by Referenced Task Tag field */ + case ISCSI_TASK_FUNC_ABORT_TASK: + SPDK_NOTICELOG("ABORT_TASK\n"); + + task->scsi.abort_id = ref_task_tag; + + spdk_iscsi_queue_mgmt_task(conn, task, SPDK_SCSI_TASK_FUNC_ABORT_TASK); + spdk_del_transfer_task(conn, ref_task_tag); + + return SPDK_SUCCESS; + + /* abort all tasks issued via this session on the LUN */ + case ISCSI_TASK_FUNC_ABORT_TASK_SET: + SPDK_NOTICELOG("ABORT_TASK_SET\n"); + + spdk_iscsi_queue_mgmt_task(conn, task, SPDK_SCSI_TASK_FUNC_ABORT_TASK_SET); + spdk_clear_all_transfer_task(conn, task->scsi.lun); + + return SPDK_SUCCESS; + + 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"); + + spdk_iscsi_queue_mgmt_task(conn, task, SPDK_SCSI_TASK_FUNC_LUN_RESET); + spdk_clear_all_transfer_task(conn, task->scsi.lun); + return SPDK_SUCCESS; + + case ISCSI_TASK_FUNC_TARGET_WARM_RESET: + SPDK_NOTICELOG("TARGET_WARM_RESET (Unsupported)\n"); + +#if 0 + spdk_iscsi_drop_conns(conn, conn->initiator_name, 1 /* drop all */); + rc = spdk_iscsi_tgt_node_reset(conn->sess->target, lun); + if (rc < 0) { + SPDK_ERRLOG("tgt_node reset failed\n"); + } +#else + task->scsi.response = SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED; +#endif + break; + + case ISCSI_TASK_FUNC_TARGET_COLD_RESET: + SPDK_NOTICELOG("TARGET_COLD_RESET\n"); + +#if 0 + spdk_iscsi_drop_conns(conn, conn->initiator_name, 1 /* drop all */); + + rc = spdk_iscsi_tgt_node_reset(conn->sess->target, lun); + if (rc < 0) { + SPDK_ERRLOG("tgt_node reset failed\n"); + } + + conn->state = ISCSI_CONN_STATE_EXITING; +#else + task->scsi.response = SPDK_SCSI_TASK_MGMT_RESP_REJECT_FUNC_NOT_SUPPORTED; +#endif + 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; + } + + spdk_iscsi_task_mgmt_response(conn, task); + spdk_iscsi_task_put(task); + return 0; +} + +static int +spdk_iscsi_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; + uint32_t transfer_tag; + uint32_t CmdSN; + int I_bit; + int data_len; + + 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; + + data_len = DGET24(reqh->data_segment_len); + if (data_len > conn->MaxRecvDataSegmentLength) { + data_len = conn->MaxRecvDataSegmentLength; + } + + lun = from_be64(&reqh->lun); + task_tag = from_be32(&reqh->itt); + transfer_tag = from_be32(&reqh->ttt); + CmdSN = from_be32(&reqh->cmd_sn); + pdu->cmd_sn = CmdSN; + + 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", + CmdSN, 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. + */ + } + + /* + * 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) { + if (I_bit == 1) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "got NOPOUT ITT=0xffffffff\n"); + return SPDK_SUCCESS; + } else { + SPDK_ERRLOG("got NOPOUT ITT=0xffffffff, I=0\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + } + + 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); + } + + transfer_tag = 0xffffffffU; + + /* response PDU */ + rsp_pdu = spdk_get_pdu(); + if (rsp_pdu == NULL) { + free(data); + return SPDK_ISCSI_CONNECTION_FATAL; + } + 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, transfer_tag); + + 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); + + spdk_iscsi_conn_write_pdu(conn, rsp_pdu); + conn->last_nopin = spdk_get_ticks(); + + return SPDK_SUCCESS; +} + +static int +spdk_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 idx; + int rc; + int data_out_req; + + transfer_len = task->scsi.transfer_len; + data_len = spdk_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 SPDK_SUCCESS; + } + + conn->data_out_cnt += data_out_req; + idx = conn->pending_r2t++; + + conn->outstanding_r2t_tasks[idx] = task; + 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 = DMIN32(max_burst_len, (transfer_len - data_len)); + rc = spdk_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); + return SPDK_SUCCESS; +} + +/* 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 +spdk_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); + spdk_add_transfer_task(conn, task); + } else { + break; + } + } +} + +void spdk_del_transfer_task(struct spdk_iscsi_conn *conn, uint32_t task_tag) +{ + struct spdk_iscsi_task *task; + int i; + + for (i = 0; i < conn->pending_r2t; i++) { + if (conn->outstanding_r2t_tasks[i]->tag == task_tag) { + task = conn->outstanding_r2t_tasks[i]; + conn->data_out_cnt -= task->data_out_cnt; + + conn->pending_r2t--; + for (; i < conn->pending_r2t; i++) { + conn->outstanding_r2t_tasks[i] = conn->outstanding_r2t_tasks[i + 1]; + } + conn->outstanding_r2t_tasks[conn->pending_r2t] = NULL; + break; + } + } + + spdk_start_queued_transfer_tasks(conn); +} + +static void +spdk_del_connection_queued_task(struct spdk_iscsi_conn *conn, void *tailq, + struct spdk_scsi_lun *lun) +{ + struct spdk_iscsi_task *task, *task_tmp; + /* + * Temporary used to index spdk_scsi_task related + * queues of the connection. + */ + TAILQ_HEAD(queued_tasks, spdk_iscsi_task) *head; + head = (struct queued_tasks *)tailq; + + TAILQ_FOREACH_SAFE(task, head, link, task_tmp) { + if (lun == NULL || lun == task->scsi.lun) { + TAILQ_REMOVE(head, task, link); + if (lun != NULL && spdk_scsi_lun_is_removing(lun)) { + spdk_scsi_task_process_null_lun(&task->scsi); + spdk_iscsi_task_response(conn, task); + } + spdk_iscsi_task_put(task); + } + } +} + +void spdk_clear_all_transfer_task(struct spdk_iscsi_conn *conn, + struct spdk_scsi_lun *lun) +{ + int i, j, pending_r2t; + struct spdk_iscsi_task *task; + + pending_r2t = conn->pending_r2t; + for (i = 0; i < pending_r2t; i++) { + task = conn->outstanding_r2t_tasks[i]; + if (lun == NULL || lun == task->scsi.lun) { + conn->outstanding_r2t_tasks[i] = NULL; + task->outstanding_r2t = 0; + task->next_r2t_offset = 0; + task->next_expected_r2t_offset = 0; + conn->data_out_cnt -= task->data_out_cnt; + conn->pending_r2t--; + } + } + + for (i = 0; i < pending_r2t; i++) { + if (conn->outstanding_r2t_tasks[i] != NULL) { + continue; + } + for (j = i + 1; j < pending_r2t; j++) { + if (conn->outstanding_r2t_tasks[j] != NULL) { + conn->outstanding_r2t_tasks[i] = conn->outstanding_r2t_tasks[j]; + conn->outstanding_r2t_tasks[j] = NULL; + break; + } + } + } + + spdk_del_connection_queued_task(conn, &conn->active_r2t_tasks, lun); + spdk_del_connection_queued_task(conn, &conn->queued_r2t_tasks, lun); + + spdk_start_queued_transfer_tasks(conn); +} + +/* This function is used to handle the r2t snack */ +static int +spdk_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 spdk_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 spdk_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 (spdk_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 +spdk_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 = spdk_iscsi_task_get_primary(task); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_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 spdk_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); + spdk_iscsi_conn_write_pdu(conn, old_pdu); + break; + } + } + } + } + return 0; +} + +/* This function is used to handle the status snack */ +static int +spdk_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 spdk_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); + spdk_iscsi_conn_write_pdu(conn, old_pdu); + } + } + + return 0; +} + +/* This function is used to handle the data ack snack */ +static int +spdk_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; + int rc; + 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 = spdk_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 = spdk_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); + if (old_pdu->task) { + spdk_iscsi_task_put(old_pdu->task); + } + spdk_put_pdu(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: + rc = spdk_iscsi_reject(conn, pdu, ISCSI_REASON_INVALID_SNACK); + if (rc < 0) { + SPDK_ERRLOG("iscsi_reject() failed\n"); + return -1; + } + + 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 * +spdk_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 +spdk_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 = spdk_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); + spdk_iscsi_conn_write_pdu(conn, pdu); + } 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 = DMIN32(conn->sess->MaxBurstLength, (transfer_len - + task->next_expected_r2t_offset)); + + /* remove the old_r2t_pdu */ + if (pdu->task) { + spdk_iscsi_task_put(pdu->task); + } + spdk_put_pdu(pdu); + + /* re-send a new r2t pdu */ + rc = spdk_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; +} + +/* This function is used to handle the snack request from the initiator */ +static int +spdk_iscsi_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"); + rc = spdk_iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + if (rc < 0) { + SPDK_ERRLOG("iscsi_reject() failed\n"); + return -1; + } + return rc; + } + + 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 = spdk_get_scsi_task_from_itt(conn, task_tag, + ISCSI_OP_SCSI_DATAIN); + if (task) { + return spdk_iscsi_handle_recovery_datain(conn, task, pdu, + beg_run, run_length, task_tag); + } + task = spdk_get_scsi_task_from_itt(conn, task_tag, ISCSI_OP_R2T); + if (task) { + return spdk_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 = spdk_iscsi_handle_status_snack(conn, pdu); + break; + case ISCSI_FLAG_SNACK_TYPE_DATA_ACK: + rc = spdk_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 = spdk_iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + break; + default: + SPDK_ERRLOG("Unknown SNACK type %d, protocol error\n", type); + rc = spdk_iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + break; + } + + return rc; +} + +/* This function is used to refree the pdu when it is acknowledged */ +static void +spdk_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 = DMIN32(ExpStatSN, conn->StatSN); + TAILQ_FOREACH_SAFE(pdu, &conn->snack_pdu_list, tailq, pdu_temp) { + stat_sn = from_be32(&pdu->bhs.stat_sn); + if (SN32_LT(stat_sn, conn->exp_statsn)) { + TAILQ_REMOVE(&conn->snack_pdu_list, pdu, tailq); + spdk_iscsi_conn_free_pdu(conn, pdu); + } + } +} + +static int spdk_iscsi_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); + + task = spdk_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 = spdk_iscsi_task_get(conn, task, spdk_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; + spdk_scsi_task_set_data(&subtask->scsi, pdu->data, pdu->data_segment_len); + spdk_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 = DMIN32(conn->sess->MaxBurstLength, (transfer_len - + task->next_r2t_offset)); + rc = spdk_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); + spdk_iscsi_task_cpl(&subtask->scsi); + return 0; + } + + spdk_iscsi_queue_task(conn, subtask); + return 0; + +send_r2t_recovery_return: + rc = spdk_iscsi_send_r2t_recovery(conn, task, task->acked_r2tsn, true); + if (rc == 0) { + return 0; + } + +reject_return: + rc = spdk_iscsi_reject(conn, pdu, reject_reason); + if (rc < 0) { + SPDK_ERRLOG("iscsi_reject() failed\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + return SPDK_SUCCESS; +} + +static int +spdk_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; + + /* R2T PDU */ + rsp_pdu = spdk_get_pdu(); + 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 */ + to_be64(&rsph->lun, task->lun_id); + 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++; + + spdk_iscsi_conn_write_pdu(conn, rsp_pdu); + + return SPDK_SUCCESS; +} + +void spdk_iscsi_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 || + !spdk_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 = spdk_get_pdu(); + rsp = (struct iscsi_bhs_nop_in *) &rsp_pdu->bhs; + rsp_pdu->data = NULL; + + /* + * spdk_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); + + spdk_iscsi_conn_write_pdu(conn, rsp_pdu); + conn->last_nopin = spdk_get_ticks(); + conn->nop_outstanding = true; +} + +static void +spdk_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; +} + +int +spdk_iscsi_execute(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu) +{ + int opcode; + int rc; + struct spdk_iscsi_pdu *rsp_pdu = NULL; + uint32_t ExpStatSN; + uint32_t QCmdSN; + int I_bit; + struct spdk_iscsi_sess *sess; + struct iscsi_bhs_scsi_req *reqh; + + if (pdu == NULL) { + return -1; + } + + opcode = pdu->bhs.opcode; + reqh = (struct iscsi_bhs_scsi_req *)&pdu->bhs; + pdu->cmd_sn = from_be32(&reqh->cmd_sn); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "opcode %x\n", opcode); + + if (opcode == ISCSI_OP_LOGIN) { + rc = spdk_iscsi_op_login(conn, pdu); + if (rc < 0) { + SPDK_ERRLOG("iscsi_op_login() failed\n"); + } + return rc; + } + + /* 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 = spdk_get_pdu(); + if (rsp_pdu == NULL) { + return SPDK_ISCSI_CONNECTION_FATAL; + } + spdk_init_login_reject_response(pdu, rsp_pdu); + spdk_iscsi_conn_write_pdu(conn, rsp_pdu); + 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"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + + sess = conn->sess; + if (!sess) { + SPDK_ERRLOG("Connection has no associated session!\n"); + return SPDK_ISCSI_CONNECTION_FATAL; + } + I_bit = reqh->immediate; + if (I_bit == 0) { + if (SN32_LT(pdu->cmd_sn, sess->ExpCmdSN) || + 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 (SN32_GT(ExpStatSN, conn->StatSN)) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "StatSN(%u) advanced\n", ExpStatSN); + ExpStatSN = conn->StatSN; + } + + if (sess->ErrorRecoveryLevel >= 1) { + spdk_remove_acked_pdu(conn, ExpStatSN); + } + + if (opcode == ISCSI_OP_NOPOUT || opcode == ISCSI_OP_SCSI) { + QCmdSN = sess->MaxCmdSN - sess->ExpCmdSN + 1; + QCmdSN += sess->queue_depth; + if (SN32_LT(ExpStatSN + QCmdSN, conn->StatSN)) { + SPDK_ERRLOG("StatSN(%u/%u) QCmdSN(%u) error\n", + ExpStatSN, conn->StatSN, QCmdSN); + return SPDK_ISCSI_CONNECTION_FATAL; + } + } + + if (!I_bit && opcode != ISCSI_OP_SCSI_DATAOUT) { + sess->ExpCmdSN++; + } + + switch (opcode) { + case ISCSI_OP_NOPOUT: + rc = spdk_iscsi_op_nopout(conn, pdu); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_op_nopout() failed\n"); + return rc; + } + break; + + case ISCSI_OP_SCSI: + rc = spdk_iscsi_op_scsi(conn, pdu); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_op_scsi() failed\n"); + return rc; + } + break; + case ISCSI_OP_TASK: + rc = spdk_iscsi_op_task(conn, pdu); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_op_task() failed\n"); + return rc; + } + break; + + case ISCSI_OP_TEXT: + rc = spdk_iscsi_op_text(conn, pdu); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_op_text() failed\n"); + return rc; + } + break; + + case ISCSI_OP_LOGOUT: + rc = spdk_iscsi_op_logout(conn, pdu); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_op_logout() failed\n"); + return rc; + } + break; + + case ISCSI_OP_SCSI_DATAOUT: + rc = spdk_iscsi_op_data(conn, pdu); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_op_data() failed\n"); + return rc; + } + break; + + case ISCSI_OP_SNACK: + rc = spdk_iscsi_op_snack(conn, pdu); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_op_snack() failed\n"); + return rc; + } + break; + + default: + SPDK_ERRLOG("unsupported opcode %x\n", opcode); + rc = spdk_iscsi_reject(conn, pdu, ISCSI_REASON_PROTOCOL_ERROR); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_reject() failed\n"); + return rc; + } + break; + } + + return 0; +} + +void spdk_free_sess(struct spdk_iscsi_sess *sess) +{ + if (sess == NULL) { + return; + } + + sess->tag = 0; + sess->target = NULL; + sess->session_type = SESSION_TYPE_INVALID; + spdk_iscsi_param_free(sess->params); + free(sess->conns); + spdk_scsi_port_free(&sess->initiator_port); + spdk_mempool_put(g_spdk_iscsi.session_pool, (void *)sess); +} + +static int +spdk_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_spdk_iscsi.session_pool); + if (!sess) { + SPDK_ERRLOG("Unable to get session object\n"); + SPDK_ERRLOG("MaxSessions set to %d\n", g_spdk_iscsi.MaxSessions); + return -ENOMEM; + } + + /* configuration values */ + pthread_mutex_lock(&g_spdk_iscsi.mutex); + + sess->MaxConnections = g_spdk_iscsi.MaxConnectionsPerSession; + sess->MaxOutstandingR2T = DEFAULT_MAXOUTSTANDINGR2T; + + sess->DefaultTime2Wait = g_spdk_iscsi.DefaultTime2Wait; + sess->DefaultTime2Retain = g_spdk_iscsi.DefaultTime2Retain; + sess->FirstBurstLength = g_spdk_iscsi.FirstBurstLength; + sess->MaxBurstLength = SPDK_ISCSI_MAX_BURST_LENGTH; + sess->InitialR2T = DEFAULT_INITIALR2T; + sess->ImmediateData = g_spdk_iscsi.ImmediateData; + sess->DataPDUInOrder = DEFAULT_DATAPDUINORDER; + sess->DataSequenceInOrder = DEFAULT_DATASEQUENCEINORDER; + sess->ErrorRecoveryLevel = g_spdk_iscsi.ErrorRecoveryLevel; + + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + + sess->tag = conn->portal->group->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 = NULL; + sess->isid = 0; + sess->session_type = session_type; + sess->current_text_itt = 0xffffffffU; + + /* set default params */ + rc = spdk_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 = spdk_iscsi_param_set_int(sess->params, "MaxConnections", + sess->MaxConnections); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + rc = spdk_iscsi_param_set_int(sess->params, "MaxOutstandingR2T", + sess->MaxOutstandingR2T); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + rc = spdk_iscsi_param_set_int(sess->params, "DefaultTime2Wait", + sess->DefaultTime2Wait); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + rc = spdk_iscsi_param_set_int(sess->params, "DefaultTime2Retain", + sess->DefaultTime2Retain); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + rc = spdk_iscsi_param_set_int(sess->params, "FirstBurstLength", + sess->FirstBurstLength); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + rc = spdk_iscsi_param_set_int(sess->params, "MaxBurstLength", + sess->MaxBurstLength); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set_int() failed\n"); + goto error_return; + } + + rc = spdk_iscsi_param_set(sess->params, "InitialR2T", + sess->InitialR2T ? "Yes" : "No"); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set() failed\n"); + goto error_return; + } + + rc = spdk_iscsi_param_set(sess->params, "ImmediateData", + sess->ImmediateData ? "Yes" : "No"); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set() failed\n"); + goto error_return; + } + + rc = spdk_iscsi_param_set(sess->params, "DataPDUInOrder", + sess->DataPDUInOrder ? "Yes" : "No"); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set() failed\n"); + goto error_return; + } + + rc = spdk_iscsi_param_set(sess->params, "DataSequenceInOrder", + sess->DataSequenceInOrder ? "Yes" : "No"); + if (rc < 0) { + SPDK_ERRLOG("iscsi_param_set() failed\n"); + goto error_return; + } + + rc = spdk_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 = spdk_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: + spdk_free_sess(sess); + conn->sess = NULL; + return -1; +} + +static struct spdk_iscsi_sess * +spdk_get_iscsi_sess_by_tsih(uint16_t tsih) +{ + struct spdk_iscsi_sess *session; + + if (tsih == 0 || tsih > g_spdk_iscsi.MaxSessions) { + return NULL; + } + + session = g_spdk_iscsi.session[tsih - 1]; + assert(tsih == session->tsih); + + return session; +} + +static int +spdk_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 = spdk_get_iscsi_sess_by_tsih(tsih); + if (sess == NULL) { + SPDK_ERRLOG("spdk_get_iscsi_sess_by_tsih failed\n"); + return -1; + } + if ((conn->portal->group->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 -1; + } + + 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 -1; + } + + 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; +} + +bool spdk_iscsi_is_deferred_free_pdu(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; +} diff --git a/src/spdk/lib/iscsi/iscsi.h b/src/spdk/lib/iscsi/iscsi.h new file mode 100644 index 00000000..3cfb20fc --- /dev/null +++ b/src/spdk/lib/iscsi/iscsi.h @@ -0,0 +1,467 @@ +/*- + * 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/event.h" +#include "spdk/thread.h" + +#include "iscsi/param.h" +#include "iscsi/tgt_node.h" + +#include "spdk/assert.h" +#include "spdk/util.h" + +#define SPDK_ISCSI_DEFAULT_NODEBASE "iqn.2016-06.io.spdk" + +#define DEFAULT_MAXR2T 4 +#define MAX_INITIATOR_NAME 256 +#define MAX_TARGET_NAME 256 + +#define MAX_PORTAL 1024 +#define MAX_INITIATOR 256 +#define MAX_NETMASK 256 +#define MAX_SESSIONS 1024 +#define MAX_ISCSI_CONNECTIONS MAX_SESSIONS +#define MAX_FIRSTBURSTLENGTH 16777215 + +#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_FIRSTBURSTLENGTH 8192 +#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 +#define DEFAULT_CONNECTIONS_PER_LCORE 4 + +/* + * 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 + +/* + * SPDK iSCSI target will only send a maximum of SPDK_BDEV_LARGE_BUF_MAX_SIZE data segments, even if the + * connection can support more. + */ +#define SPDK_ISCSI_MAX_SEND_DATA_SEGMENT_LENGTH SPDK_BDEV_LARGE_BUF_MAX_SIZE + +/* + * 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 + +/* + * Defines default maximum queue depth per connection and this can be + * changed by configuration file. + */ +#define DEFAULT_MAX_QUEUE_DEPTH 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 + +/** 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 */ + +/* according to RFC1982 */ +#define SN32_CMPMAX (((uint32_t)1U) << (32 - 1)) +#define SN32_LT(S1,S2) \ + (((uint32_t)(S1) != (uint32_t)(S2)) \ + && (((uint32_t)(S1) < (uint32_t)(S2) \ + && ((uint32_t)(S2) - (uint32_t)(S1) < SN32_CMPMAX)) \ + || ((uint32_t)(S1) > (uint32_t)(S2) \ + && ((uint32_t)(S1) - (uint32_t)(S2) > SN32_CMPMAX)))) +#define SN32_GT(S1,S2) \ + (((uint32_t)(S1) != (uint32_t)(S2)) \ + && (((uint32_t)(S1) < (uint32_t)(S2) \ + && ((uint32_t)(S2) - (uint32_t)(S1) > SN32_CMPMAX)) \ + || ((uint32_t)(S1) > (uint32_t)(S2) \ + && ((uint32_t)(S1) - (uint32_t)(S2) < SN32_CMPMAX)))) + +/* 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; + size_t len; + uint64_t reserved; /* do not use */ +}; + +struct spdk_iscsi_pdu { + struct iscsi_bhs bhs; + struct spdk_mobj *mobj; + 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; + int 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; + 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; +}; + +enum iscsi_connection_state { + ISCSI_CONN_STATE_INVALID = 0, + ISCSI_CONN_STATE_RUNNING = 1, + ISCSI_CONN_STATE_LOGGED_OUT = 2, + ISCSI_CONN_STATE_EXITING = 3, + ISCSI_CONN_STATE_EXITED = 4, +}; + +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 { + uint32_t core; + struct spdk_poller *poller; + struct spdk_poller *nop_poller; + STAILQ_HEAD(connections, spdk_iscsi_conn) connections; + struct spdk_sock_group *sock_group; +}; + +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; + uint32_t min_connections_per_core; +}; + +struct spdk_iscsi_globals { + char *authfile; + char *nodebase; + pthread_mutex_t mutex; + 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; + + 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; + struct spdk_iscsi_poll_group *poll_group; +}; + +#define ISCSI_SECURITY_NEGOTIATION_PHASE 0 +#define ISCSI_OPERATIONAL_NEGOTIATION_PHASE 1 +#define ISCSI_NSG_RESERVED_CODE 2 +#define ISCSI_FULL_FEATURE_PHASE 3 + +enum spdk_error_codes { + SPDK_SUCCESS = 0, + 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_spdk_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 spdk_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 *spdk_iscsi_opts_alloc(void); +void spdk_iscsi_opts_free(struct spdk_iscsi_opts *opts); +struct spdk_iscsi_opts *spdk_iscsi_opts_copy(struct spdk_iscsi_opts *src); +void spdk_iscsi_opts_info_json(struct spdk_json_write_ctx *w); +int spdk_iscsi_set_discovery_auth(bool disable_chap, bool require_chap, + bool mutual_chap, int32_t chap_group); +int spdk_iscsi_chap_get_authinfo(struct iscsi_chap_auth *auth, const char *authuser, + int ag_tag); +int spdk_iscsi_add_auth_group(int32_t tag, struct spdk_iscsi_auth_group **_group); +struct spdk_iscsi_auth_group *spdk_iscsi_find_auth_group_by_tag(int32_t tag); +void spdk_iscsi_delete_auth_group(struct spdk_iscsi_auth_group *group); +int spdk_iscsi_auth_group_add_secret(struct spdk_iscsi_auth_group *group, + const char *user, const char *secret, + const char *muser, const char *msecret); +int spdk_iscsi_auth_group_delete_secret(struct spdk_iscsi_auth_group *group, + const char *user); +void spdk_iscsi_auth_groups_info_json(struct spdk_json_write_ctx *w); + +void spdk_iscsi_send_nopin(struct spdk_iscsi_conn *conn); +void spdk_iscsi_task_response(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task); +int spdk_iscsi_execute(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu *pdu); +int spdk_iscsi_build_iovecs(struct spdk_iscsi_conn *conn, + struct iovec *iovec, struct spdk_iscsi_pdu *pdu); +int +spdk_iscsi_read_pdu(struct spdk_iscsi_conn *conn, struct spdk_iscsi_pdu **_pdu); +void spdk_iscsi_task_mgmt_response(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_task *task); + +int spdk_iscsi_conn_params_init(struct iscsi_param **params); +int spdk_iscsi_sess_params_init(struct iscsi_param **params); + +void spdk_free_sess(struct spdk_iscsi_sess *sess); +void spdk_clear_all_transfer_task(struct spdk_iscsi_conn *conn, + struct spdk_scsi_lun *lun); +void spdk_del_transfer_task(struct spdk_iscsi_conn *conn, uint32_t CmdSN); +bool spdk_iscsi_is_deferred_free_pdu(struct spdk_iscsi_pdu *pdu); + +int spdk_iscsi_negotiate_params(struct spdk_iscsi_conn *conn, + struct iscsi_param **params_p, uint8_t *data, + int alloc_len, int data_len); +int spdk_iscsi_copy_param2var(struct spdk_iscsi_conn *conn); + +void spdk_iscsi_task_cpl(struct spdk_scsi_task *scsi_task); +void spdk_iscsi_task_mgmt_cpl(struct spdk_scsi_task *scsi_task); + +/* Memory management */ +void spdk_put_pdu(struct spdk_iscsi_pdu *pdu); +struct spdk_iscsi_pdu *spdk_get_pdu(void); +int spdk_iscsi_conn_handle_queued_datain_tasks(struct spdk_iscsi_conn *conn); + +static inline int +spdk_get_immediate_data_buffer_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_spdk_iscsi.FirstBurstLength + + ISCSI_DIGEST_LEN + /* data digest */ + ISCSI_DIGEST_LEN + /* header digest */ + 8 + /* bidirectional AHS */ + 52; /* extended CDB AHS (for a 64-byte CDB) */ +} + +static inline int +spdk_get_data_out_buffer_size(void) +{ + return SPDK_ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH; +} + +#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 00000000..dd9777a3 --- /dev/null +++ b/src/spdk/lib/iscsi/iscsi_rpc.c @@ -0,0 +1,1542 @@ +/*- + * 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/event.h" +#include "spdk/string.h" +#include "spdk_internal/log.h" + +static void +spdk_rpc_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, + "get_initiator_groups requires no parameters"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_json_write_array_begin(w); + spdk_iscsi_init_grps_info_json(w); + spdk_json_write_array_end(w); + + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("get_initiator_groups", spdk_rpc_get_initiator_groups, SPDK_RPC_RUNTIME) + +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 +spdk_rpc_add_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 (spdk_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); + if (w == NULL) { + return; + } + + 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("add_initiator_group", spdk_rpc_add_initiator_group, SPDK_RPC_RUNTIME) + +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 +spdk_rpc_add_initiators_to_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_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 (spdk_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); + if (w == NULL) { + return; + } + + 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("add_initiators_to_initiator_group", + spdk_rpc_add_initiators_to_initiator_group, SPDK_RPC_RUNTIME) + +static void +spdk_rpc_delete_initiators_from_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_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 (spdk_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); + if (w == NULL) { + return; + } + + 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("delete_initiators_from_initiator_group", + spdk_rpc_delete_initiators_from_initiator_group, SPDK_RPC_RUNTIME) + +struct rpc_delete_initiator_group { + int32_t tag; +}; + +static const struct spdk_json_object_decoder rpc_delete_initiator_group_decoders[] = { + {"tag", offsetof(struct rpc_delete_initiator_group, tag), spdk_json_decode_int32}, +}; + +static void +spdk_rpc_delete_initiator_group(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_delete_initiator_group req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_init_grp *ig; + + if (spdk_json_decode_object(params, rpc_delete_initiator_group_decoders, + SPDK_COUNTOF(rpc_delete_initiator_group_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + ig = spdk_iscsi_init_grp_unregister(req.tag); + if (!ig) { + goto invalid; + } + spdk_iscsi_tgt_node_delete_map(NULL, ig); + spdk_iscsi_init_grp_destroy(ig); + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + 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("delete_initiator_group", spdk_rpc_delete_initiator_group, SPDK_RPC_RUNTIME) + +static void +spdk_rpc_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, + "get_target_nodes requires no parameters"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_json_write_array_begin(w); + spdk_iscsi_tgt_nodes_info_json(w); + spdk_json_write_array_end(w); + + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("get_target_nodes", spdk_rpc_get_target_nodes, SPDK_RPC_RUNTIME) + +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_CONSTRUCT_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_CONSTRUCT_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_CONSTRUCT_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 +spdk_rpc_construct_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_CONSTRUCT_TARGET_NODE_MAX_LUN] = {0}; + int32_t lun_ids[RPC_CONSTRUCT_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 = spdk_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); + if (w == NULL) { + return; + } + + 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("construct_target_node", spdk_rpc_construct_target_node, SPDK_RPC_RUNTIME) + +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 +spdk_rpc_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 = spdk_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 = spdk_iscsi_tgt_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); + if (w != NULL) { + 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("add_pg_ig_maps", spdk_rpc_add_pg_ig_maps, SPDK_RPC_RUNTIME) + +static void +spdk_rpc_delete_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 = spdk_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 = spdk_iscsi_tgt_node_delete_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); + if (w != NULL) { + 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("delete_pg_ig_maps", spdk_rpc_delete_pg_ig_maps, SPDK_RPC_RUNTIME) + +struct rpc_delete_target_node { + char *name; +}; + +static void +free_rpc_delete_target_node(struct rpc_delete_target_node *r) +{ + free(r->name); +} + +static const struct spdk_json_object_decoder rpc_delete_target_node_decoders[] = { + {"name", offsetof(struct rpc_delete_target_node, name), spdk_json_decode_string}, +}; + +static void +spdk_rpc_delete_target_node(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_delete_target_node req = {}; + struct spdk_json_write_ctx *w; + + if (spdk_json_decode_object(params, rpc_delete_target_node_decoders, + SPDK_COUNTOF(rpc_delete_target_node_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + if (req.name == NULL) { + SPDK_ERRLOG("missing name param\n"); + goto invalid; + } + + if (spdk_iscsi_shutdown_tgt_node_by_name(req.name)) { + SPDK_ERRLOG("shutdown_tgt_node_by_name failed\n"); + goto invalid; + } + + free_rpc_delete_target_node(&req); + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + 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_delete_target_node(&req); +} +SPDK_RPC_REGISTER("delete_target_node", spdk_rpc_delete_target_node, SPDK_RPC_RUNTIME) + +static void +spdk_rpc_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, + "get_portal_groups requires no parameters"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_json_write_array_begin(w); + spdk_iscsi_portal_grps_info_json(w); + spdk_json_write_array_end(w); + + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("get_portal_groups", spdk_rpc_get_portal_groups, SPDK_RPC_RUNTIME) + +struct rpc_portal { + char *host; + char *port; + char *cpumask; +}; + +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); + free(portal->cpumask); +} + +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}, + {"cpumask", offsetof(struct rpc_portal, cpumask), spdk_json_decode_string, true}, +}; + +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 +spdk_rpc_add_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 = spdk_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 = spdk_iscsi_portal_create(req.portal_list.portals[i].host, + req.portal_list.portals[i].port, + req.portal_list.portals[i].cpumask); + if (portal == NULL) { + SPDK_ERRLOG("portal_create failed\n"); + goto out; + } + spdk_iscsi_portal_grp_add_portal(pg, portal); + } + + rc = spdk_iscsi_portal_grp_open(pg); + if (rc != 0) { + SPDK_ERRLOG("portal_grp_open failed\n"); + goto out; + } + + rc = spdk_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); + if (w != NULL) { + 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) { + spdk_iscsi_portal_grp_release(pg); + } + } + free_rpc_portal_group(&req); +} +SPDK_RPC_REGISTER("add_portal_group", spdk_rpc_add_portal_group, SPDK_RPC_RUNTIME) + +struct rpc_delete_portal_group { + int32_t tag; +}; + +static const struct spdk_json_object_decoder rpc_delete_portal_group_decoders[] = { + {"tag", offsetof(struct rpc_delete_portal_group, tag), spdk_json_decode_int32}, +}; + +static void +spdk_rpc_delete_portal_group(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_delete_portal_group req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_portal_grp *pg; + + if (spdk_json_decode_object(params, rpc_delete_portal_group_decoders, + SPDK_COUNTOF(rpc_delete_portal_group_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + goto invalid; + } + + pg = spdk_iscsi_portal_grp_unregister(req.tag); + if (!pg) { + goto invalid; + } + + spdk_iscsi_tgt_node_delete_map(pg, NULL); + spdk_iscsi_portal_grp_release(pg); + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + 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("delete_portal_group", spdk_rpc_delete_portal_group, SPDK_RPC_RUNTIME) + +static void +spdk_rpc_get_iscsi_connections(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct spdk_json_write_ctx *w; + struct spdk_iscsi_conn *conns = g_conns_array; + int i; + uint16_t tsih; + + if (params != NULL) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "get_iscsi_connections requires no parameters"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_json_write_array_begin(w); + + for (i = 0; i < MAX_ISCSI_CONNECTIONS; i++) { + struct spdk_iscsi_conn *c = &conns[i]; + + if (!c->is_valid) { + continue; + } + + spdk_json_write_object_begin(w); + + spdk_json_write_name(w, "id"); + spdk_json_write_int32(w, c->id); + + spdk_json_write_name(w, "cid"); + spdk_json_write_int32(w, c->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 c->sess. + */ + if (c->sess == NULL) { + tsih = -1; + } else { + tsih = c->sess->tsih; + } + spdk_json_write_name(w, "tsih"); + spdk_json_write_int32(w, tsih); + + spdk_json_write_name(w, "lcore_id"); + spdk_json_write_int32(w, c->lcore); + + spdk_json_write_name(w, "initiator_addr"); + spdk_json_write_string(w, c->initiator_addr); + + spdk_json_write_name(w, "target_addr"); + spdk_json_write_string(w, c->target_addr); + + spdk_json_write_name(w, "target_node_name"); + spdk_json_write_string(w, c->target_short_name); + + spdk_json_write_object_end(w); + } + spdk_json_write_array_end(w); + + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("get_iscsi_connections", spdk_rpc_get_iscsi_connections, SPDK_RPC_RUNTIME) + +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 +spdk_rpc_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 = spdk_iscsi_find_tgt_node(req.name); + if (target == NULL) { + SPDK_ERRLOG("target is not found\n"); + goto invalid; + } + + rc = spdk_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); + if (w == NULL) { + return; + } + + 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("target_node_add_lun", spdk_rpc_target_node_add_lun, SPDK_RPC_RUNTIME) + +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 +spdk_rpc_set_iscsi_target_node_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"); + return; + } + + target = spdk_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); + free_rpc_target_auth(&req); + return; + } + + rc = spdk_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"); + free_rpc_target_auth(&req); + return; + } + + free_rpc_target_auth(&req); + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("set_iscsi_target_node_auth", spdk_rpc_set_iscsi_target_node_auth, + SPDK_RPC_RUNTIME) + +static void +spdk_rpc_get_iscsi_global_params(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, + "get_iscsi_global_params requires no parameters"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_iscsi_opts_info_json(w); + + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("get_iscsi_global_params", spdk_rpc_get_iscsi_global_params, SPDK_RPC_RUNTIME) + +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 +spdk_rpc_set_iscsi_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 = spdk_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); + if (w == NULL) { + return; + } + + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("set_iscsi_discovery_auth", spdk_rpc_set_iscsi_discovery_auth, SPDK_RPC_RUNTIME) + + +#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 +spdk_rpc_add_iscsi_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_spdk_iscsi.mutex); + + rc = spdk_iscsi_add_auth_group(req.tag, &group); + if (rc != 0) { + pthread_mutex_unlock(&g_spdk_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 = spdk_iscsi_auth_group_add_secret(group, _secret->user, _secret->secret, + _secret->muser, _secret->msecret); + if (rc != 0) { + spdk_iscsi_delete_auth_group(group); + pthread_mutex_unlock(&g_spdk_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_spdk_iscsi.mutex); + + free_rpc_auth_group(&req); + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("add_iscsi_auth_group", spdk_rpc_add_iscsi_auth_group, SPDK_RPC_RUNTIME) + +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 +spdk_rpc_delete_iscsi_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_spdk_iscsi.mutex); + + group = spdk_iscsi_find_auth_group_by_tag(req.tag); + if (group == NULL) { + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Could not find auth group (%d)", req.tag); + return; + } + + spdk_iscsi_delete_auth_group(group); + + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("delete_iscsi_auth_group", spdk_rpc_delete_iscsi_auth_group, SPDK_RPC_RUNTIME) + +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 +spdk_rpc_add_secret_to_iscsi_auth_group(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_spdk_iscsi.mutex); + + group = spdk_iscsi_find_auth_group_by_tag(req.tag); + if (group == NULL) { + pthread_mutex_unlock(&g_spdk_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 = spdk_iscsi_auth_group_add_secret(group, req.user, req.secret, req.muser, req.msecret); + if (rc != 0) { + pthread_mutex_unlock(&g_spdk_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_spdk_iscsi.mutex); + + free_rpc_add_auth_secret(&req); + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("add_secret_to_iscsi_auth_group", spdk_rpc_add_secret_to_iscsi_auth_group, + SPDK_RPC_RUNTIME) + +struct rpc_delete_auth_secret { + int32_t tag; + char *user; +}; + +static void +free_rpc_delete_auth_secret(struct rpc_delete_auth_secret *_secret) +{ + free(_secret->user); +} + +static const struct spdk_json_object_decoder rpc_delete_auth_secret_decoders[] = { + {"tag", offsetof(struct rpc_delete_auth_secret, tag), spdk_json_decode_int32}, + {"user", offsetof(struct rpc_delete_auth_secret, user), spdk_json_decode_string}, +}; + +static void +spdk_rpc_delete_secret_from_iscsi_auth_group(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_delete_auth_secret req = {}; + struct spdk_json_write_ctx *w; + struct spdk_iscsi_auth_group *group; + int rc; + + if (spdk_json_decode_object(params, rpc_delete_auth_secret_decoders, + SPDK_COUNTOF(rpc_delete_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_delete_auth_secret(&req); + return; + } + + pthread_mutex_lock(&g_spdk_iscsi.mutex); + + group = spdk_iscsi_find_auth_group_by_tag(req.tag); + if (group == NULL) { + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + + spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, + "Could not find auth group (%d)", req.tag); + free_rpc_delete_auth_secret(&req); + return; + } + + rc = spdk_iscsi_auth_group_delete_secret(group, req.user); + if (rc != 0) { + pthread_mutex_unlock(&g_spdk_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_delete_auth_secret(&req); + return; + } + + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + + free_rpc_delete_auth_secret(&req); + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_json_write_bool(w, true); + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("delete_secret_from_iscsi_auth_group", + spdk_rpc_delete_secret_from_iscsi_auth_group, SPDK_RPC_RUNTIME) + +static void +spdk_rpc_get_iscsi_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, + "get_iscsi_auth_groups requires no parameters"); + return; + } + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_json_write_array_begin(w); + spdk_iscsi_auth_groups_info_json(w); + spdk_json_write_array_end(w); + + spdk_jsonrpc_end_result(request, w); +} +SPDK_RPC_REGISTER("get_iscsi_auth_groups", spdk_rpc_get_iscsi_auth_groups, SPDK_RPC_RUNTIME) diff --git a/src/spdk/lib/iscsi/iscsi_subsystem.c b/src/spdk/lib/iscsi/iscsi_subsystem.c new file mode 100644 index 00000000..6cfa4f93 --- /dev/null +++ b/src/spdk/lib/iscsi/iscsi_subsystem.c @@ -0,0 +1,1523 @@ +/*- + * 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 "spdk_internal/event.h" +#include "spdk_internal/log.h" + +struct spdk_iscsi_opts *g_spdk_iscsi_opts = 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 +spdk_iscsi_globals_config_text(FILE *fp) +{ + const char *authmethod = "None"; + char authgroup[32] = "None"; + + if (NULL == fp) { + return; + } + + if (g_spdk_iscsi.require_chap) { + authmethod = "CHAP"; + } else if (g_spdk_iscsi.mutual_chap) { + authmethod = "CHAP Mutual"; + } else if (!g_spdk_iscsi.disable_chap) { + authmethod = "Auto"; + } + + if (g_spdk_iscsi.chap_group) { + snprintf(authgroup, sizeof(authgroup), "AuthGroup%d", g_spdk_iscsi.chap_group); + } + + fprintf(fp, ISCSI_CONFIG_TMPL, + g_spdk_iscsi.nodebase, + g_spdk_iscsi.authfile ? "AuthFile" : "", + g_spdk_iscsi.authfile ? g_spdk_iscsi.authfile : "", + g_spdk_iscsi.timeout, authmethod, authgroup, + g_spdk_iscsi.MaxSessions, g_spdk_iscsi.MaxConnectionsPerSession, + g_spdk_iscsi.MaxConnections, + g_spdk_iscsi.MaxQueueDepth, + g_spdk_iscsi.DefaultTime2Wait, g_spdk_iscsi.DefaultTime2Retain, + g_spdk_iscsi.FirstBurstLength, + (g_spdk_iscsi.ImmediateData) ? "Yes" : "No", + g_spdk_iscsi.ErrorRecoveryLevel); +} + +static void +spdk_mobj_ctor(struct spdk_mempool *mp, __attribute__((unused)) void *arg, + void *_m, __attribute__((unused)) unsigned i) +{ + struct spdk_mobj *m = _m; + uint64_t *phys_addr; + ptrdiff_t off; + + m->mp = mp; + m->buf = (uint8_t *)m + sizeof(struct spdk_mobj); + m->buf = (void *)((unsigned long)((uint8_t *)m->buf + 512) & ~511UL); + off = (uint64_t)(uint8_t *)m->buf - (uint64_t)(uint8_t *)m; + + /* + * we store the physical address in a 64bit unsigned integer + * right before the 512B aligned buffer area. + */ + phys_addr = (uint64_t *)m->buf - 1; + *phys_addr = spdk_vtophys(m) + off; +} + +#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 spdk_iscsi_initialize_pdu_pool(void) +{ + struct spdk_iscsi_globals *iscsi = &g_spdk_iscsi; + int imm_mobj_size = spdk_get_immediate_data_buffer_size() + + sizeof(struct spdk_mobj) + 512; + int dout_mobj_size = spdk_get_data_out_buffer_size() + + sizeof(struct spdk_mobj) + 512; + + /* 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, 0, + spdk_env_get_socket_id(spdk_env_get_current_core()), + spdk_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_get_socket_id(spdk_env_get_current_core()), + spdk_mobj_ctor, NULL); + if (!iscsi->pdu_data_out_pool) { + SPDK_ERRLOG("create PDU data out pool failed\n"); + return -1; + } + + return 0; +} + +static void spdk_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 +spdk_iscsi_initialize_task_pool(void) +{ + struct spdk_iscsi_globals *iscsi = &g_spdk_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 spdk_iscsi_initialize_session_pool(void) +{ + struct spdk_iscsi_globals *iscsi = &g_spdk_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, + spdk_iscsi_sess_ctor, iscsi); + if (!iscsi->session_pool) { + SPDK_ERRLOG("create session pool failed\n"); + return -1; + } + + return 0; +} + +static int +spdk_iscsi_initialize_all_pools(void) +{ + if (spdk_iscsi_initialize_pdu_pool() != 0) { + return -1; + } + + if (spdk_iscsi_initialize_session_pool() != 0) { + return -1; + } + + if (spdk_iscsi_initialize_task_pool() != 0) { + return -1; + } + + return 0; +} + +static void +spdk_iscsi_check_pool(struct spdk_mempool *pool, size_t count) +{ + if (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 +spdk_iscsi_check_pools(void) +{ + struct spdk_iscsi_globals *iscsi = &g_spdk_iscsi; + + spdk_iscsi_check_pool(iscsi->pdu_pool, PDU_POOL_SIZE(iscsi)); + spdk_iscsi_check_pool(iscsi->session_pool, SESSION_POOL_SIZE(iscsi)); + spdk_iscsi_check_pool(iscsi->pdu_immediate_data_pool, IMMEDIATE_DATA_POOL_SIZE(iscsi)); + spdk_iscsi_check_pool(iscsi->pdu_data_out_pool, DATA_OUT_POOL_SIZE(iscsi)); + spdk_iscsi_check_pool(iscsi->task_pool, DEFAULT_TASK_POOL_SIZE); +} + +static void +spdk_iscsi_free_pools(void) +{ + struct spdk_iscsi_globals *iscsi = &g_spdk_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 spdk_put_pdu(struct spdk_iscsi_pdu *pdu) +{ + if (!pdu) { + return; + } + + pdu->ref--; + + if (pdu->ref < 0) { + SPDK_ERRLOG("Negative PDU refcount: %p\n", pdu); + pdu->ref = 0; + } + + 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_spdk_iscsi.pdu_pool, (void *)pdu); + } +} + +struct spdk_iscsi_pdu *spdk_get_pdu(void) +{ + struct spdk_iscsi_pdu *pdu; + + pdu = spdk_mempool_get(g_spdk_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; + + return pdu; +} + +static void +spdk_iscsi_log_globals(void) +{ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "AuthFile %s\n", + g_spdk_iscsi.authfile ? g_spdk_iscsi.authfile : "(none)"); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "NodeBase %s\n", g_spdk_iscsi.nodebase); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "MaxSessions %d\n", g_spdk_iscsi.MaxSessions); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "MaxConnectionsPerSession %d\n", + g_spdk_iscsi.MaxConnectionsPerSession); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "MaxQueueDepth %d\n", g_spdk_iscsi.MaxQueueDepth); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "DefaultTime2Wait %d\n", + g_spdk_iscsi.DefaultTime2Wait); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "DefaultTime2Retain %d\n", + g_spdk_iscsi.DefaultTime2Retain); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "FirstBurstLength %d\n", + g_spdk_iscsi.FirstBurstLength); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "ImmediateData %s\n", + g_spdk_iscsi.ImmediateData ? "Yes" : "No"); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "AllowDuplicateIsid %s\n", + g_spdk_iscsi.AllowDuplicateIsid ? "Yes" : "No"); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "ErrorRecoveryLevel %d\n", + g_spdk_iscsi.ErrorRecoveryLevel); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "Timeout %d\n", g_spdk_iscsi.timeout); + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "NopInInterval %d\n", + g_spdk_iscsi.nopininterval); + if (g_spdk_iscsi.disable_chap) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "DiscoveryAuthMethod None\n"); + } else if (!g_spdk_iscsi.require_chap) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "DiscoveryAuthMethod Auto\n"); + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "DiscoveryAuthMethod %s %s\n", + g_spdk_iscsi.require_chap ? "CHAP" : "", + g_spdk_iscsi.mutual_chap ? "Mutual" : ""); + } + + if (g_spdk_iscsi.chap_group == 0) { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "DiscoveryAuthGroup None\n"); + } else { + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, + "DiscoveryAuthGroup AuthGroup%d\n", + g_spdk_iscsi.chap_group); + } + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "MinConnectionsPerCore%d\n", + spdk_iscsi_conn_get_min_per_core()); +} + +static void +spdk_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 = DEFAULT_FIRSTBURSTLENGTH; + 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; + opts->min_connections_per_core = DEFAULT_CONNECTIONS_PER_LCORE; +} + +struct spdk_iscsi_opts * +spdk_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; + } + + spdk_iscsi_opts_init(opts); + + return opts; +} + +void +spdk_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 * +spdk_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; + dst->min_connections_per_core = src->min_connections_per_core; + + return dst; +} + +static int +spdk_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; + int min_conn_per_core = 0; + 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) { + 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; + } + } + } + min_conn_per_core = spdk_conf_section_get_intval(sp, "MinConnectionsPerCore"); + if (min_conn_per_core >= 0) { + opts->min_connections_per_core = min_conn_per_core; + } + + return 0; +} + +static int +spdk_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 (!spdk_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 +spdk_iscsi_parse_options(struct spdk_iscsi_opts **popts) +{ + struct spdk_iscsi_opts *opts; + struct spdk_conf_section *sp; + int rc; + + opts = spdk_iscsi_opts_alloc(); + if (!opts) { + SPDK_ERRLOG("spdk_iscsi_opts_alloc_failed() failed\n"); + return -ENOMEM; + } + + /* Process parameters */ + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_iscsi_read_config_file_parmas\n"); + sp = spdk_conf_find_section(NULL, "iSCSI"); + if (sp != NULL) { + rc = spdk_iscsi_read_config_file_params(sp, opts); + if (rc != 0) { + free(opts); + SPDK_ERRLOG("spdk_iscsi_read_config_file_params() failed\n"); + return rc; + } + } + + *popts = opts; + + return 0; +} + +static int +spdk_iscsi_set_global_params(struct spdk_iscsi_opts *opts) +{ + int rc; + + rc = spdk_iscsi_opts_verify(opts); + if (rc != 0) { + SPDK_ERRLOG("spdk_iscsi_opts_verify() failed\n"); + return rc; + } + + if (opts->authfile != NULL) { + g_spdk_iscsi.authfile = strdup(opts->authfile); + if (!g_spdk_iscsi.authfile) { + SPDK_ERRLOG("failed to strdup for auth file %s\n", opts->authfile); + return -ENOMEM; + } + } + + g_spdk_iscsi.nodebase = strdup(opts->nodebase); + if (!g_spdk_iscsi.nodebase) { + SPDK_ERRLOG("failed to strdup for nodebase %s\n", opts->nodebase); + return -ENOMEM; + } + + g_spdk_iscsi.MaxSessions = opts->MaxSessions; + g_spdk_iscsi.MaxConnectionsPerSession = opts->MaxConnectionsPerSession; + g_spdk_iscsi.MaxQueueDepth = opts->MaxQueueDepth; + g_spdk_iscsi.DefaultTime2Wait = opts->DefaultTime2Wait; + g_spdk_iscsi.DefaultTime2Retain = opts->DefaultTime2Retain; + g_spdk_iscsi.FirstBurstLength = opts->FirstBurstLength; + g_spdk_iscsi.ImmediateData = opts->ImmediateData; + g_spdk_iscsi.AllowDuplicateIsid = opts->AllowDuplicateIsid; + g_spdk_iscsi.ErrorRecoveryLevel = opts->ErrorRecoveryLevel; + g_spdk_iscsi.timeout = opts->timeout; + g_spdk_iscsi.nopininterval = opts->nopininterval; + g_spdk_iscsi.disable_chap = opts->disable_chap; + g_spdk_iscsi.require_chap = opts->require_chap; + g_spdk_iscsi.mutual_chap = opts->mutual_chap; + g_spdk_iscsi.chap_group = opts->chap_group; + + spdk_iscsi_conn_set_min_per_core(opts->min_connections_per_core); + + spdk_iscsi_log_globals(); + + return 0; +} + +int +spdk_iscsi_set_discovery_auth(bool disable_chap, bool require_chap, bool mutual_chap, + int32_t chap_group) +{ + if (!spdk_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_spdk_iscsi.mutex); + g_spdk_iscsi.disable_chap = disable_chap; + g_spdk_iscsi.require_chap = require_chap; + g_spdk_iscsi.mutual_chap = mutual_chap; + g_spdk_iscsi.chap_group = chap_group; + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + + return 0; +} + +int +spdk_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 +spdk_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 +spdk_iscsi_add_auth_group(int32_t tag, struct spdk_iscsi_auth_group **_group) +{ + struct spdk_iscsi_auth_group *group; + + TAILQ_FOREACH(group, &g_spdk_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_spdk_iscsi.auth_group_head, group, tailq); + + *_group = group; + return 0; +} + +void +spdk_iscsi_delete_auth_group(struct spdk_iscsi_auth_group *group) +{ + struct spdk_iscsi_auth_secret *_secret, *tmp; + + TAILQ_REMOVE(&g_spdk_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 * +spdk_iscsi_find_auth_group_by_tag(int32_t tag) +{ + struct spdk_iscsi_auth_group *group; + + TAILQ_FOREACH(group, &g_spdk_iscsi.auth_group_head, tailq) { + if (group->tag == tag) { + return group; + } + } + + return NULL; +} + +static void +spdk_iscsi_auth_groups_destroy(void) +{ + struct spdk_iscsi_auth_group *group, *tmp; + + TAILQ_FOREACH_SAFE(group, &g_spdk_iscsi.auth_group_head, tailq, tmp) { + spdk_iscsi_delete_auth_group(group); + } +} + +static int +spdk_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 = spdk_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 = spdk_iscsi_auth_group_add_secret(group, user, secret, muser, msecret); + if (rc != 0) { + SPDK_ERRLOG("Failed to add secret to auth group\n"); + spdk_iscsi_delete_auth_group(group); + return rc; + } + } + + return 0; +} + +static int +spdk_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_spdk_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"); + spdk_iscsi_auth_groups_destroy(); + spdk_conf_free(config); + return -EINVAL; + } + + rc = spdk_iscsi_parse_auth_group(sp); + if (rc != 0) { + SPDK_ERRLOG("parse_auth_group() failed\n"); + spdk_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 * +spdk_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_spdk_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 +spdk_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_spdk_iscsi.mutex); + + _secret = spdk_iscsi_find_auth_secret(authuser, ag_tag); + if (_secret == NULL) { + pthread_mutex_unlock(&g_spdk_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_spdk_iscsi.mutex); + return 0; +} + +static int +spdk_iscsi_initialize_global_params(void) +{ + int rc; + + if (!g_spdk_iscsi_opts) { + rc = spdk_iscsi_parse_options(&g_spdk_iscsi_opts); + if (rc != 0) { + SPDK_ERRLOG("spdk_iscsi_parse_options() failed\n"); + return rc; + } + } + + rc = spdk_iscsi_set_global_params(g_spdk_iscsi_opts); + if (rc != 0) { + SPDK_ERRLOG("spdk_iscsi_set_global_params() failed\n"); + } + + spdk_iscsi_opts_free(g_spdk_iscsi_opts); + g_spdk_iscsi_opts = NULL; + + return rc; +} + +static void +spdk_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 int +spdk_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 0; + } + + 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, link, tmp) { + if (conn->state == ISCSI_CONN_STATE_EXITING) { + spdk_iscsi_conn_destruct(conn); + } + } + + return -1; +} + +static int +spdk_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, link, tmp) { + spdk_iscsi_conn_handle_nop(conn); + } + + return -1; +} + +static void +iscsi_create_poll_group(void *ctx) +{ + struct spdk_iscsi_poll_group *pg; + + assert(g_spdk_iscsi.poll_group != NULL); + pg = &g_spdk_iscsi.poll_group[spdk_env_get_current_core()]; + pg->core = spdk_env_get_current_core(); + + STAILQ_INIT(&pg->connections); + pg->sock_group = spdk_sock_group_create(); + assert(pg->sock_group != NULL); + + pg->poller = spdk_poller_register(spdk_iscsi_poll_group_poll, pg, 0); + /* set the period to 1 sec */ + pg->nop_poller = spdk_poller_register(spdk_iscsi_poll_group_handle_nop, pg, 1000000); +} + +static void +iscsi_unregister_poll_group(void *ctx) +{ + struct spdk_iscsi_poll_group *pg; + + assert(g_spdk_iscsi.poll_group != NULL); + pg = &g_spdk_iscsi.poll_group[spdk_env_get_current_core()]; + 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); +} + +static void +spdk_initialize_iscsi_poll_group(spdk_thread_fn cpl) +{ + size_t g_num_poll_groups = spdk_env_get_last_core() + 1; + + g_spdk_iscsi.poll_group = calloc(g_num_poll_groups, sizeof(struct spdk_iscsi_poll_group)); + if (!g_spdk_iscsi.poll_group) { + SPDK_ERRLOG("Failed to allocated iscsi poll group\n"); + spdk_iscsi_init_complete(-1); + return; + } + + /* Send a message to each thread and create a poll group */ + spdk_for_each_thread(iscsi_create_poll_group, NULL, cpl); +} + +static void +spdk_iscsi_parse_configuration(void *ctx) +{ + int rc; + + rc = spdk_iscsi_parse_portal_grps(); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_parse_portal_grps() failed\n"); + goto end; + } + + rc = spdk_iscsi_parse_init_grps(); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_parse_init_grps() failed\n"); + goto end; + } + + rc = spdk_iscsi_parse_tgt_nodes(); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_parse_tgt_nodes() failed\n"); + } + + if (g_spdk_iscsi.authfile != NULL) { + if (access(g_spdk_iscsi.authfile, R_OK) == 0) { + rc = spdk_iscsi_parse_auth_info(); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_parse_auth_info() failed\n"); + } + } else { + SPDK_INFOLOG(SPDK_LOG_ISCSI, "CHAP secret file is not found in the path %s\n", + g_spdk_iscsi.authfile); + } + } + +end: + spdk_iscsi_init_complete(rc); +} + +static int +spdk_iscsi_parse_globals(void) +{ + int rc; + + rc = spdk_iscsi_initialize_global_params(); + if (rc != 0) { + SPDK_ERRLOG("spdk_iscsi_initialize_iscsi_global_params() failed\n"); + return rc; + } + + g_spdk_iscsi.session = spdk_dma_zmalloc(sizeof(void *) * g_spdk_iscsi.MaxSessions, 0, NULL); + if (!g_spdk_iscsi.session) { + SPDK_ERRLOG("spdk_dma_zmalloc() 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_spdk_iscsi.MaxConnections = g_spdk_iscsi.MaxSessions; + + rc = spdk_iscsi_initialize_all_pools(); + if (rc != 0) { + SPDK_ERRLOG("spdk_initialize_all_pools() failed\n"); + return -1; + } + + rc = spdk_initialize_iscsi_conns(); + if (rc < 0) { + SPDK_ERRLOG("spdk_initialize_iscsi_conns() failed\n"); + return rc; + } + + spdk_initialize_iscsi_poll_group(spdk_iscsi_parse_configuration); + 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 = spdk_iscsi_parse_globals(); + if (rc < 0) { + SPDK_ERRLOG("spdk_iscsi_parse_globals() failed\n"); + spdk_iscsi_init_complete(-1); + } + + /* + * spdk_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; + + spdk_iscsi_portal_grp_close_all(); + spdk_shutdown_iscsi_conns(); +} + +static void +spdk_iscsi_fini_done(void *arg) +{ + spdk_iscsi_check_pools(); + spdk_iscsi_free_pools(); + + spdk_iscsi_shutdown_tgt_nodes(); + spdk_iscsi_init_grps_destroy(); + spdk_iscsi_portal_grps_destroy(); + spdk_iscsi_auth_groups_destroy(); + free(g_spdk_iscsi.authfile); + free(g_spdk_iscsi.nodebase); + free(g_spdk_iscsi.poll_group); + + pthread_mutex_destroy(&g_spdk_iscsi.mutex); + g_fini_cb_fn(g_fini_cb_arg); +} + +void +spdk_shutdown_iscsi_conns_done(void) +{ + if (g_spdk_iscsi.poll_group) { + spdk_for_each_thread(iscsi_unregister_poll_group, NULL, spdk_iscsi_fini_done); + } else { + spdk_iscsi_fini_done(NULL); + } +} + +void +spdk_iscsi_config_text(FILE *fp) +{ + spdk_iscsi_globals_config_text(fp); + spdk_iscsi_portal_grps_config_text(fp); + spdk_iscsi_init_grps_config_text(fp); + spdk_iscsi_tgt_nodes_config_text(fp); +} + +void +spdk_iscsi_opts_info_json(struct spdk_json_write_ctx *w) +{ + spdk_json_write_object_begin(w); + + if (g_spdk_iscsi.authfile != NULL) { + spdk_json_write_named_string(w, "auth_file", g_spdk_iscsi.authfile); + } + spdk_json_write_named_string(w, "node_base", g_spdk_iscsi.nodebase); + + spdk_json_write_named_uint32(w, "max_sessions", g_spdk_iscsi.MaxSessions); + spdk_json_write_named_uint32(w, "max_connections_per_session", + g_spdk_iscsi.MaxConnectionsPerSession); + + spdk_json_write_named_uint32(w, "max_queue_depth", g_spdk_iscsi.MaxQueueDepth); + + spdk_json_write_named_uint32(w, "default_time2wait", g_spdk_iscsi.DefaultTime2Wait); + spdk_json_write_named_uint32(w, "default_time2retain", g_spdk_iscsi.DefaultTime2Retain); + + spdk_json_write_named_uint32(w, "first_burst_length", g_spdk_iscsi.FirstBurstLength); + + spdk_json_write_named_bool(w, "immediate_data", g_spdk_iscsi.ImmediateData); + + spdk_json_write_named_bool(w, "allow_duplicated_isid", g_spdk_iscsi.AllowDuplicateIsid); + + spdk_json_write_named_uint32(w, "error_recovery_level", g_spdk_iscsi.ErrorRecoveryLevel); + + spdk_json_write_named_int32(w, "nop_timeout", g_spdk_iscsi.timeout); + spdk_json_write_named_int32(w, "nop_in_interval", g_spdk_iscsi.nopininterval); + + spdk_json_write_named_bool(w, "disable_chap", g_spdk_iscsi.disable_chap); + spdk_json_write_named_bool(w, "require_chap", g_spdk_iscsi.require_chap); + spdk_json_write_named_bool(w, "mutual_chap", g_spdk_iscsi.mutual_chap); + spdk_json_write_named_int32(w, "chap_group", g_spdk_iscsi.chap_group); + + spdk_json_write_named_uint32(w, "min_connections_per_core", + spdk_iscsi_conn_get_min_per_core()); + + spdk_json_write_object_end(w); +} + +static void +spdk_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 +spdk_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", "add_iscsi_auth_group"); + + spdk_json_write_name(w, "params"); + spdk_iscsi_auth_group_info_json(group, w); + + spdk_json_write_object_end(w); +} + +void +spdk_iscsi_auth_groups_info_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_auth_group *group; + + TAILQ_FOREACH(group, &g_spdk_iscsi.auth_group_head, tailq) { + spdk_iscsi_auth_group_info_json(group, w); + } +} + +static void +spdk_iscsi_auth_groups_config_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_auth_group *group; + + TAILQ_FOREACH(group, &g_spdk_iscsi.auth_group_head, tailq) { + spdk_iscsi_auth_group_config_json(group, w); + } +} + +static void +spdk_iscsi_opts_config_json(struct spdk_json_write_ctx *w) +{ + spdk_json_write_object_begin(w); + + spdk_json_write_named_string(w, "method", "set_iscsi_options"); + + spdk_json_write_name(w, "params"); + spdk_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); + spdk_iscsi_opts_config_json(w); + spdk_iscsi_portal_grps_config_json(w); + spdk_iscsi_init_grps_config_json(w); + spdk_iscsi_tgt_nodes_config_json(w); + spdk_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 00000000..2b3291e4 --- /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 spdk_md5init(struct spdk_md5ctx *md5ctx) +{ + int rc; + + if (md5ctx == NULL) { + return -1; + } + rc = MD5_Init(&md5ctx->md5ctx); + return rc; +} + +int spdk_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 spdk_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 00000000..ff571b4a --- /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 spdk_md5init(struct spdk_md5ctx *md5ctx); +int spdk_md5final(void *md5, struct spdk_md5ctx *md5ctx); +int spdk_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 00000000..e09bf899 --- /dev/null +++ b/src/spdk/lib/iscsi/param.c @@ -0,0 +1,1182 @@ +/*- + * 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 +spdk_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 +spdk_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 * +spdk_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 +spdk_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; + spdk_iscsi_param_free(param); + return 0; + } + prev_param = param; + } + return -1; +} + +int +spdk_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 = spdk_iscsi_param_find(*params, key); + if (param != NULL) { + spdk_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 +spdk_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 = spdk_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 +spdk_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 = spdk_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 +spdk_iscsi_parse_param(struct iscsi_param **params, const uint8_t *data) +{ + int rc; + uint8_t *key_copy; + const uint8_t *key_end, *val; + int key_len, val_len; + int max_len; + + key_end = strchr(data, '='); + 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 != spdk_iscsi_param_find(*params, key_copy)) { + SPDK_ERRLOG("Duplicated Key %s\n", key_copy); + free(key_copy); + return -1; + } + + val = key_end + 1; /* +1 to skip over the '=' */ + val_len = strlen(val); + /* + * 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 = spdk_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; + } + + rc = spdk_iscsi_param_add(params, key_copy, val, NULL, 0); + 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 +spdk_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 = spdk_iscsi_parse_param(params, p); + 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' + * index of last '\0' is len -1. + */ + for (i = len - 2; data[i] != '\0' && i > 0; i--) { + ; + } + *partial_parameter = xstrdup(&data[i == 0 ? 0 : i + 1]); + len = (i == 0 ? 0 : i + 1); + } + + while (offset < len && data[offset] != '\0') { + rc = spdk_iscsi_parse_param(params, data + offset); + if (rc < 0) { + return -1; + } + offset += rc; + } + return 0; +} + +char * +spdk_iscsi_param_get_val(struct iscsi_param *params, const char *key) +{ + struct iscsi_param *param; + + param = spdk_iscsi_param_find(params, key); + if (param == NULL) { + return NULL; + } + return param->val; +} + +int +spdk_iscsi_param_eq_val(struct iscsi_param *params, const char *key, + const char *val) +{ + struct iscsi_param *param; + + param = spdk_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 +spdk_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 = spdk_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 = spdk_iscsi_param_find(*params, table[i].key); + if (param != NULL) { + param->state_index = i; + } else { + SPDK_ERRLOG("spdk_iscsi_param_find() failed\n"); + return -1; + } + } + + return 0; +} + +int +spdk_iscsi_conn_params_init(struct iscsi_param **params) +{ + return spdk_iscsi_params_init_internal(params, &conn_param_table[0]); +} + +int +spdk_iscsi_sess_params_init(struct iscsi_param **params) +{ + return spdk_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", + 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 +spdk_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 = spdk_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 = spdk_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; + +} + +/** + * spdk_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 +spdk_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 *spdk_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 *spdk_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 *spdk_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 * +spdk_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 = spdk_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 = spdk_iscsi_negotiate_param_numerical(add_param_value, + param, + valid_list, + in_val, + cur_val); + break; + + case ISPT_BOOLEAN_OR: + new_val = spdk_iscsi_negotiate_param_boolean(add_param_value, + param, + in_val, + cur_val, + "Yes"); + break; + case ISPT_BOOLEAN_AND: + new_val = spdk_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 +spdk_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 = spdk_iscsi_param_find(*params_dst_p, param->key); + if (*cur_param_p == NULL) { + *params_dst_p = conn->sess->params; + *cur_param_p = spdk_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] && + !spdk_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] && + !spdk_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 +spdk_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 (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 = spdk_iscsi_param_find(*params, "SessionType"); + if (cur_param == NULL) { + cur_param = spdk_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 = spdk_iscsi_param_find(*params, "MaxBurstLength"); + if (param != NULL) { + param = spdk_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; + spdk_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 (spdk_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 && + spdk_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 = spdk_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 = spdk_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 < MAX_FIRSTBURSTLENGTH && + 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 (spdk_iscsi_find_key_in_array(param->key, target_declarative_params)) { + add_param_value = 1; + } + + new_val = spdk_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) { + spdk_iscsi_param_set(params_dst, param->key, new_val); + } + total = spdk_iscsi_construct_data_from_param(param, + new_val, + data, + alloc_len, + total); + if (total < 0) { + goto final_return; + } + + total = spdk_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 +spdk_iscsi_copy_param2var(struct spdk_iscsi_conn *conn) +{ + const char *val; + + val = spdk_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_ISCSI_MAX_SEND_DATA_SEGMENT_LENGTH) { + conn->MaxRecvDataSegmentLength = SPDK_ISCSI_MAX_SEND_DATA_SEGMENT_LENGTH; + } + + val = spdk_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 = spdk_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 = spdk_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 = spdk_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 = spdk_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 = spdk_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 = spdk_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 = spdk_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 00000000..c9dc8cab --- /dev/null +++ b/src/spdk/lib/iscsi/param.h @@ -0,0 +1,84 @@ +/*- + * 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" + +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 +spdk_iscsi_param_free(struct iscsi_param *params); +struct iscsi_param * +spdk_iscsi_param_find(struct iscsi_param *params, const char *key); +int +spdk_iscsi_param_del(struct iscsi_param **params, const char *key); +int +spdk_iscsi_param_add(struct iscsi_param **params, const char *key, + const char *val, const char *list, int type); +int +spdk_iscsi_param_set(struct iscsi_param *params, const char *key, + const char *val); +int +spdk_iscsi_param_set_int(struct iscsi_param *params, const char *key, uint32_t val); +int +spdk_iscsi_parse_params(struct iscsi_param **params, const uint8_t *data, + int len, bool cbit_enabled, char **partial_parameter); +char * +spdk_iscsi_param_get_val(struct iscsi_param *params, const char *key); +int +spdk_iscsi_param_eq_val(struct iscsi_param *params, const char *key, + const char *val); + +#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 00000000..60a724c9 --- /dev/null +++ b/src/spdk/lib/iscsi/portal_grp.c @@ -0,0 +1,707 @@ +/*- + * 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/event.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/acceptor.h" + +#define PORTNUMSTRLEN 32 + +static struct spdk_iscsi_portal * +spdk_iscsi_portal_find_by_addr(const char *host, const char *port) +{ + struct spdk_iscsi_portal *p; + + TAILQ_FOREACH(p, &g_spdk_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 * +spdk_iscsi_portal_create(const char *host, const char *port, const char *cpumask) +{ + struct spdk_iscsi_portal *p = NULL, *tmp; + struct spdk_cpuset *core_mask = NULL; + int rc; + + assert(host != NULL); + assert(port != 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)"); + p->host = strdup("[::]"); + } 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)"); + p->host = strdup("0.0.0.0"); + } else { + p->host = strdup(host); + } + if (!p->host) { + SPDK_ERRLOG("strdup() failed for host\n"); + goto error_out; + } + + p->port = strdup(port); + if (!p->port) { + SPDK_ERRLOG("strdup() failed for host\n"); + goto error_out; + } + + core_mask = spdk_cpuset_alloc(); + if (!core_mask) { + SPDK_ERRLOG("spdk_cpuset_alloc() failed for host\n"); + goto error_out; + } + + if (cpumask != NULL) { + rc = spdk_app_parse_core_mask(cpumask, core_mask); + if (rc < 0) { + SPDK_ERRLOG("cpumask (%s) is invalid\n", cpumask); + goto error_out; + } + if (spdk_cpuset_count(core_mask) == 0) { + SPDK_ERRLOG("cpumask (%s) does not contain core mask (0x%s)\n", + cpumask, spdk_cpuset_fmt(spdk_app_get_core_mask())); + goto error_out; + } + } else { + spdk_cpuset_copy(core_mask, spdk_app_get_core_mask()); + } + + p->cpumask = core_mask; + + p->sock = NULL; + p->group = NULL; /* set at a later time by caller */ + p->acceptor_poller = NULL; + + pthread_mutex_lock(&g_spdk_iscsi.mutex); + tmp = spdk_iscsi_portal_find_by_addr(host, port); + if (tmp != NULL) { + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + SPDK_ERRLOG("portal (%s, %s) already exists\n", host, port); + goto error_out; + } + + TAILQ_INSERT_TAIL(&g_spdk_iscsi.portal_head, p, g_tailq); + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + + return p; + +error_out: + spdk_cpuset_free(core_mask); + free(p->port); + free(p->host); + free(p); + + return NULL; +} + +void +spdk_iscsi_portal_destroy(struct spdk_iscsi_portal *p) +{ + assert(p != NULL); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_iscsi_portal_destroy\n"); + + pthread_mutex_lock(&g_spdk_iscsi.mutex); + TAILQ_REMOVE(&g_spdk_iscsi.portal_head, p, g_tailq); + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + + free(p->host); + free(p->port); + spdk_cpuset_free(p->cpumask); + free(p); + +} + +static int +spdk_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); + 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. + */ + spdk_iscsi_acceptor_start(p); + + return 0; +} + +static void +spdk_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_iscsi_acceptor_stop(p); + spdk_sock_close(&p->sock); + } +} + +static int +spdk_iscsi_parse_portal(const char *portalstring, struct spdk_iscsi_portal **ip, + int dry_run) +{ + char *host = NULL, *port = NULL, *cpumask = NULL; + int len, rc = -1; + const char *p, *q; + + 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); + } + } + + if (!dry_run) { + 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') { + if (!dry_run) { + port = malloc(PORTNUMSTRLEN); + if (!port) { + SPDK_ERRLOG("malloc() failed for port\n"); + goto error_out; + } + snprintf(port, PORTNUMSTRLEN, "%d", DEFAULT_PORT); + } + } else { + if (p[0] != ':') { + SPDK_ERRLOG("portal error\n"); + goto error_out; + } + q = strchr(portalstring, '@'); + if (q == NULL) { + q = portalstring + strlen(portalstring); + } + if (q == p) { + SPDK_ERRLOG("no port specified\n"); + goto error_out; + } + + if (!dry_run) { + len = q - p - 1; + port = malloc(len + 1); + if (port == NULL) { + SPDK_ERRLOG("malloc() failed for port\n"); + goto error_out; + } + memcpy(port, p + 1, len); + port[len] = '\0'; + } + } + + /* Cpumask (IPv4 and IPv6 are the same) */ + p = strchr(portalstring, '@'); + if (p != NULL) { + q = portalstring + strlen(portalstring); + if (q == p) { + SPDK_ERRLOG("no cpumask specified\n"); + goto error_out; + } + if (!dry_run) { + len = q - p - 1; + cpumask = malloc(len + 1); + if (cpumask == NULL) { + SPDK_ERRLOG("malloc() failed for cpumask\n"); + goto error_out; + } + memcpy(cpumask, p + 1, len); + cpumask[len] = '\0'; + } + } + + if (!dry_run) { + *ip = spdk_iscsi_portal_create(host, port, cpumask); + if (!*ip) { + goto error_out; + } + } + + rc = 0; +error_out: + free(host); + free(port); + free(cpumask); + + return rc; +} + +struct spdk_iscsi_portal_grp * +spdk_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; + + TAILQ_INIT(&pg->head); + + return pg; +} + +void +spdk_iscsi_portal_grp_destroy(struct spdk_iscsi_portal_grp *pg) +{ + struct spdk_iscsi_portal *p; + + assert(pg != NULL); + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_iscsi_portal_grp_destroy\n"); + while (!TAILQ_EMPTY(&pg->head)) { + p = TAILQ_FIRST(&pg->head); + TAILQ_REMOVE(&pg->head, p, per_pg_tailq); + spdk_iscsi_portal_destroy(p); + } + free(pg); +} + +int +spdk_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_spdk_iscsi.mutex); + tmp = spdk_iscsi_portal_grp_find_by_tag(pg->tag); + if (tmp == NULL) { + TAILQ_INSERT_TAIL(&g_spdk_iscsi.pg_head, pg, tailq); + rc = 0; + } + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + return rc; +} + +void +spdk_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); +} + +static int +spdk_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 portals = 0, 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); + } + + /* counts number of definitions */ + for (i = 0; ; i++) { + /* + * label is no longer used, but we keep it in the config + * file definition so that we do not break existing config + * files. + */ + 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 = spdk_iscsi_parse_portal(portal, &p, 1); + if (rc < 0) { + SPDK_ERRLOG("parse portal error (%s)\n", portal); + return -1; + } + } + + portals = i; + if (portals > MAX_PORTAL) { + SPDK_ERRLOG("%d > MAX_PORTAL\n", portals); + return -1; + } + + pg = spdk_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 < portals; 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) { + SPDK_ERRLOG("portal error\n"); + goto error; + } + + rc = spdk_iscsi_parse_portal(portal, &p, 0); + 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)); + + spdk_iscsi_portal_grp_add_portal(pg, p); + } + + rc = spdk_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 = spdk_iscsi_portal_grp_register(pg); + if (rc != 0) { + SPDK_ERRLOG("register portal failed\n"); + goto error; + } + + return 0; + +error: + spdk_iscsi_portal_grp_release(pg); + return -1; +} + +struct spdk_iscsi_portal_grp * +spdk_iscsi_portal_grp_find_by_tag(int tag) +{ + struct spdk_iscsi_portal_grp *pg; + + TAILQ_FOREACH(pg, &g_spdk_iscsi.pg_head, tailq) { + if (pg->tag == tag) { + return pg; + } + } + + return NULL; +} + +int +spdk_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 = spdk_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 +spdk_iscsi_portal_grps_destroy(void) +{ + struct spdk_iscsi_portal_grp *pg; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_iscsi_portal_grps_destroy\n"); + pthread_mutex_lock(&g_spdk_iscsi.mutex); + while (!TAILQ_EMPTY(&g_spdk_iscsi.pg_head)) { + pg = TAILQ_FIRST(&g_spdk_iscsi.pg_head); + TAILQ_REMOVE(&g_spdk_iscsi.pg_head, pg, tailq); + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + spdk_iscsi_portal_grp_destroy(pg); + pthread_mutex_lock(&g_spdk_iscsi.mutex); + } + pthread_mutex_unlock(&g_spdk_iscsi.mutex); +} + +int +spdk_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 = spdk_iscsi_portal_open(p); + if (rc < 0) { + return rc; + } + } + return 0; +} + +static void +spdk_iscsi_portal_grp_close(struct spdk_iscsi_portal_grp *pg) +{ + struct spdk_iscsi_portal *p; + + TAILQ_FOREACH(p, &pg->head, per_pg_tailq) { + spdk_iscsi_portal_close(p); + } +} + +void +spdk_iscsi_portal_grp_close_all(void) +{ + struct spdk_iscsi_portal_grp *pg; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_iscsi_portal_grp_close_all\n"); + pthread_mutex_lock(&g_spdk_iscsi.mutex); + TAILQ_FOREACH(pg, &g_spdk_iscsi.pg_head, tailq) { + spdk_iscsi_portal_grp_close(pg); + } + pthread_mutex_unlock(&g_spdk_iscsi.mutex); +} + +struct spdk_iscsi_portal_grp * +spdk_iscsi_portal_grp_unregister(int tag) +{ + struct spdk_iscsi_portal_grp *pg; + + pthread_mutex_lock(&g_spdk_iscsi.mutex); + TAILQ_FOREACH(pg, &g_spdk_iscsi.pg_head, tailq) { + if (pg->tag == tag) { + TAILQ_REMOVE(&g_spdk_iscsi.pg_head, pg, tailq); + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + return pg; + } + } + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + return NULL; +} + +void +spdk_iscsi_portal_grp_release(struct spdk_iscsi_portal_grp *pg) +{ + spdk_iscsi_portal_grp_close(pg); + spdk_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 and\n" + "# a cpumask. If the port is omitted, 3260 will be used. Cpumask will\n" + "# be used to set the processor affinity of the iSCSI connection\n" + "# through the portal. If the cpumask is omitted, cpumask will be\n" + "# set to all available processors.\n" + "# Syntax:\n" + "# Portal <Name> <IP address>[:<port>[@<cpumask>]]\n"; + +#define PORTAL_GROUP_TMPL \ +"[PortalGroup%d]\n" \ +" Comment \"Portal%d\"\n" + +#define PORTAL_TMPL \ +" Portal DA1 %s:%s@0x%s\n" + +void +spdk_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_spdk_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, + spdk_cpuset_fmt(p->cpumask)); + } + } +} + +static void +spdk_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_named_string_fmt(w, "cpumask", "0x%s", + spdk_cpuset_fmt(portal->cpumask)); + + spdk_json_write_object_end(w); + } + spdk_json_write_array_end(w); + + spdk_json_write_object_end(w); +} + +static void +spdk_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", "add_portal_group"); + + spdk_json_write_name(w, "params"); + spdk_iscsi_portal_grp_info_json(pg, w); + + spdk_json_write_object_end(w); +} + +void +spdk_iscsi_portal_grps_info_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_portal_grp *pg; + + TAILQ_FOREACH(pg, &g_spdk_iscsi.pg_head, tailq) { + spdk_iscsi_portal_grp_info_json(pg, w); + } +} + +void +spdk_iscsi_portal_grps_config_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_portal_grp *pg; + + TAILQ_FOREACH(pg, &g_spdk_iscsi.pg_head, tailq) { + spdk_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 00000000..08cb3992 --- /dev/null +++ b/src/spdk/lib/iscsi/portal_grp.h @@ -0,0 +1,83 @@ +/*- + * 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" + +struct spdk_json_write_ctx; + +struct spdk_iscsi_portal { + struct spdk_iscsi_portal_grp *group; + char *host; + char *port; + struct spdk_sock *sock; + struct spdk_cpuset *cpumask; + 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; + TAILQ_ENTRY(spdk_iscsi_portal_grp) tailq; + TAILQ_HEAD(, spdk_iscsi_portal) head; +}; + +/* SPDK iSCSI Portal Group management API */ + +struct spdk_iscsi_portal *spdk_iscsi_portal_create(const char *host, const char *port, + const char *cpumask); +void spdk_iscsi_portal_destroy(struct spdk_iscsi_portal *p); + +struct spdk_iscsi_portal_grp *spdk_iscsi_portal_grp_create(int tag); +void spdk_iscsi_portal_grp_add_portal(struct spdk_iscsi_portal_grp *pg, + struct spdk_iscsi_portal *p); +void spdk_iscsi_portal_grp_destroy(struct spdk_iscsi_portal_grp *pg); +void spdk_iscsi_portal_grp_release(struct spdk_iscsi_portal_grp *pg); +int spdk_iscsi_parse_portal_grps(void); +void spdk_iscsi_portal_grps_destroy(void); +int spdk_iscsi_portal_grp_register(struct spdk_iscsi_portal_grp *pg); +struct spdk_iscsi_portal_grp *spdk_iscsi_portal_grp_unregister(int tag); +struct spdk_iscsi_portal_grp *spdk_iscsi_portal_grp_find_by_tag(int tag); +int spdk_iscsi_portal_grp_open(struct spdk_iscsi_portal_grp *pg); + +void spdk_iscsi_portal_grp_close_all(void); +void spdk_iscsi_portal_grps_config_text(FILE *fp); +void spdk_iscsi_portal_grps_info_json(struct spdk_json_write_ctx *w); +void spdk_iscsi_portal_grps_config_json(struct spdk_json_write_ctx *w); +#endif // SPDK_PORTAL_GRP_H diff --git a/src/spdk/lib/iscsi/task.c b/src/spdk/lib/iscsi/task.c new file mode 100644 index 00000000..6b56cd97 --- /dev/null +++ b/src/spdk/lib/iscsi/task.c @@ -0,0 +1,88 @@ +/*- + * 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 +spdk_iscsi_task_free(struct spdk_scsi_task *scsi_task) +{ + struct spdk_iscsi_task *task = spdk_iscsi_task_from_scsi_task(scsi_task); + + if (task->parent) { + spdk_scsi_task_put(&task->parent->scsi); + task->parent = NULL; + } + + spdk_iscsi_task_disassociate_pdu(task); + assert(task->conn->pending_task_cnt > 0); + task->conn->pending_task_cnt--; + spdk_mempool_put(g_spdk_iscsi.task_pool, (void *)task); +} + +struct spdk_iscsi_task * +spdk_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_spdk_iscsi.task_pool); + if (!task) { + SPDK_ERRLOG("Unable to get task\n"); + abort(); + } + + 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, + spdk_iscsi_task_free); + if (parent) { + parent->scsi.ref++; + task->parent = parent; + task->tag = parent->tag; + 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; + } + + return task; +} diff --git a/src/spdk/lib/iscsi/task.h b/src/spdk/lib/iscsi/task.h new file mode 100644 index 00000000..fea928ac --- /dev/null +++ b/src/spdk/lib/iscsi/task.h @@ -0,0 +1,187 @@ +/*- + * 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; + + 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; + + 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; + + 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 +spdk_iscsi_task_put(struct spdk_iscsi_task *task) +{ + spdk_scsi_task_put(&task->scsi); +} + +static inline struct spdk_iscsi_pdu * +spdk_iscsi_task_get_pdu(struct spdk_iscsi_task *task) +{ + return task->pdu; +} + +static inline void +spdk_iscsi_task_set_pdu(struct spdk_iscsi_task *task, struct spdk_iscsi_pdu *pdu) +{ + task->pdu = pdu; +} + +static inline struct iscsi_bhs * +spdk_iscsi_task_get_bhs(struct spdk_iscsi_task *task) +{ + return &spdk_iscsi_task_get_pdu(task)->bhs; +} + +static inline void +spdk_iscsi_task_associate_pdu(struct spdk_iscsi_task *task, struct spdk_iscsi_pdu *pdu) +{ + spdk_iscsi_task_set_pdu(task, pdu); + pdu->ref++; +} + +static inline void +spdk_iscsi_task_disassociate_pdu(struct spdk_iscsi_task *task) +{ + if (spdk_iscsi_task_get_pdu(task)) { + spdk_put_pdu(spdk_iscsi_task_get_pdu(task)); + spdk_iscsi_task_set_pdu(task, NULL); + } +} + +static inline int +spdk_iscsi_task_is_immediate(struct spdk_iscsi_task *task) +{ + struct iscsi_bhs_scsi_req *scsi_req; + + scsi_req = (struct iscsi_bhs_scsi_req *)spdk_iscsi_task_get_bhs(task); + return (scsi_req->immediate == 1); +} + +static inline int +spdk_iscsi_task_is_read(struct spdk_iscsi_task *task) +{ + struct iscsi_bhs_scsi_req *scsi_req; + + scsi_req = (struct iscsi_bhs_scsi_req *)spdk_iscsi_task_get_bhs(task); + return (scsi_req->read_bit == 1); +} + +static inline uint32_t +spdk_iscsi_task_get_cmdsn(struct spdk_iscsi_task *task) +{ + return spdk_iscsi_task_get_pdu(task)->cmd_sn; +} + +struct spdk_iscsi_task *spdk_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 * +spdk_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 * +spdk_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 00000000..97b5bbe1 --- /dev/null +++ b/src/spdk/lib/iscsi/tgt_node.c @@ -0,0 +1,1538 @@ +/*- + * 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 1024 +#define MAX_MASKBUF 128 + +static bool +spdk_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 +spdk_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 +spdk_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 (spdk_iscsi_ipv6_netmask_allow_addr(netmask, addr)) { + return true; + } + } else { + /* IPv4 */ + if (spdk_iscsi_ipv4_netmask_allow_addr(netmask, addr)) { + return true; + } + } + return false; +} + +static bool +spdk_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 (spdk_iscsi_netmask_allow_addr(imask->mask, addr)) { + return true; + } + } + return false; +} + +static int +spdk_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 * +spdk_iscsi_tgt_node_find_pg_map(struct spdk_iscsi_tgt_node *target, + struct spdk_iscsi_portal_grp *pg); + +bool +spdk_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 = spdk_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 = spdk_iscsi_init_grp_allow_iscsi_name(ig_map->ig, iqn, &allowed); + if (rc == 0) { + if (allowed == false) { + goto denied; + } else { + if (spdk_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->portal->group->tag); + return false; +} + +static bool +spdk_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 = spdk_iscsi_init_grp_allow_iscsi_name(ig_map->ig, iqn, &result); + if (rc == 0) { + return result; + } + } + } + + return false; +} + +int +spdk_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; + } + + if (alloc_len - total < 1) { + SPDK_ERRLOG("data space small %d\n", alloc_len); + return total; + } + + pthread_mutex_lock(&g_spdk_iscsi.mutex); + TAILQ_FOREACH(target, &g_spdk_iscsi.target_head, tailq) { + if (strcasecmp(tiqn, "ALL") != 0 + && strcasecmp(tiqn, target->name) != 0) { + continue; + } + rc = spdk_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_spdk_iscsi.mutex); + SPDK_ERRLOG("data space small %d\n", alloc_len); + return total; + } + 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_spdk_iscsi.mutex); + + return total; +} + +struct spdk_iscsi_tgt_node * +spdk_iscsi_find_tgt_node(const char *target_name) +{ + struct spdk_iscsi_tgt_node *target; + + if (target_name == NULL) { + return NULL; + } + TAILQ_FOREACH(target, &g_spdk_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 +spdk_iscsi_tgt_node_register(struct spdk_iscsi_tgt_node *target) +{ + pthread_mutex_lock(&g_spdk_iscsi.mutex); + + if (spdk_iscsi_find_tgt_node(target->name) != NULL) { + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + return -EEXIST; + } + + TAILQ_INSERT_TAIL(&g_spdk_iscsi.target_head, target, tailq); + + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + return 0; +} + +static int +spdk_iscsi_tgt_node_unregister(struct spdk_iscsi_tgt_node *target) +{ + struct spdk_iscsi_tgt_node *t; + + TAILQ_FOREACH(t, &g_spdk_iscsi.target_head, tailq) { + if (t == target) { + TAILQ_REMOVE(&g_spdk_iscsi.target_head, t, tailq); + return 0; + } + } + + return -1; +} + +static struct spdk_iscsi_ig_map * +spdk_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 * +spdk_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 (spdk_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 +_spdk_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 +spdk_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 = spdk_iscsi_pg_map_find_ig_map(pg_map, ig); + if (ig_map == NULL) { + return -ENOENT; + } + + _spdk_iscsi_pg_map_delete_ig_map(pg_map, ig_map); + return 0; +} + +static void +spdk_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) { + _spdk_iscsi_pg_map_delete_ig_map(pg_map, ig_map); + } +} + +static struct spdk_iscsi_pg_map * +spdk_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 * +spdk_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 (spdk_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 +_spdk_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 +spdk_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 = spdk_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); + } + + spdk_iscsi_pg_map_delete_all_ig_maps(pg_map); + _spdk_iscsi_tgt_node_delete_pg_map(target, pg_map); + return 0; +} + +static void +spdk_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) { + spdk_iscsi_pg_map_delete_ig_map(pg_map, ig); + if (pg_map->num_ig_maps == 0) { + _spdk_iscsi_tgt_node_delete_pg_map(target, pg_map); + } + } +} + +static void +spdk_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) { + spdk_iscsi_pg_map_delete_all_ig_maps(pg_map); + _spdk_iscsi_tgt_node_delete_pg_map(target, pg_map); + } +} + +static void +spdk_iscsi_tgt_node_destruct(struct spdk_iscsi_tgt_node *target) +{ + if (target == NULL) { + return; + } + + free(target->name); + free(target->alias); + spdk_iscsi_tgt_node_delete_all_pg_maps(target); + spdk_scsi_dev_destruct(target->dev); + + pthread_mutex_destroy(&target->mutex); + free(target); +} + +static int +spdk_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 = spdk_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 = spdk_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 = spdk_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 = spdk_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; + } + + _spdk_iscsi_pg_map_delete_ig_map(pg_map, ig_map); + if (pg_map->num_ig_maps == 0) { + _spdk_iscsi_tgt_node_delete_pg_map(target, pg_map); + } + + return 0; +} + +static int +spdk_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 = spdk_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 = spdk_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 = spdk_iscsi_tgt_node_find_pg_map(target, pg); + if (pg_map == NULL) { + pg_map = spdk_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 = spdk_iscsi_pg_map_add_ig_map(pg_map, ig); + if (ig_map == NULL) { + goto failed; + } + + return 0; + +failed: + if (new_pg_map) { + _spdk_iscsi_tgt_node_delete_pg_map(target, pg_map); + } + + return -1; +} + +int +spdk_iscsi_tgt_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_spdk_iscsi.mutex); + for (i = 0; i < num_maps; i++) { + rc = spdk_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_spdk_iscsi.mutex); + return 0; + +invalid: + for (; i > 0; --i) { + spdk_iscsi_tgt_node_delete_pg_ig_map(target, pg_tag_list[i - 1], + ig_tag_list[i - 1]); + } + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + return -1; +} + +int +spdk_iscsi_tgt_node_delete_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_spdk_iscsi.mutex); + for (i = 0; i < num_maps; i++) { + rc = spdk_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_spdk_iscsi.mutex); + return 0; + +invalid: + for (; i > 0; --i) { + rc = spdk_iscsi_tgt_node_add_pg_ig_map(target, pg_tag_list[i - 1], + ig_tag_list[i - 1]); + if (rc != 0) { + spdk_iscsi_tgt_node_delete_all_pg_maps(target); + break; + } + } + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + return -1; +} + +static int +spdk_check_iscsi_name(const char *name) +{ + const unsigned char *up = (const unsigned char *) name; + size_t n; + + /* 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 +spdk_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; +} + +_spdk_iscsi_tgt_node * +spdk_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 (!spdk_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_spdk_iscsi.nodebase, name); + } else { + snprintf(fullname, sizeof(fullname), "%s", name); + } + + if (spdk_check_iscsi_name(fullname) != 0) { + SPDK_ERRLOG("TargetName %s contains an invalid character or format.\n", + name); + return NULL; + } + + target = malloc(sizeof(*target)); + if (!target) { + SPDK_ERRLOG("could not allocate target\n"); + return NULL; + } + + memset(target, 0, sizeof(*target)); + + rc = pthread_mutex_init(&target->mutex, NULL); + if (rc != 0) { + SPDK_ERRLOG("tgt_node%d: mutex_init() failed\n", target->num); + spdk_iscsi_tgt_node_destruct(target); + return NULL; + } + + target->num = target_index; + + target->name = strdup(fullname); + if (!target->name) { + SPDK_ERRLOG("Could not allocate TargetName\n"); + spdk_iscsi_tgt_node_destruct(target); + return NULL; + } + + if (alias == NULL) { + target->alias = NULL; + } else { + target->alias = strdup(alias); + if (!target->alias) { + SPDK_ERRLOG("Could not allocate TargetAlias\n"); + spdk_iscsi_tgt_node_destruct(target); + return NULL; + } + } + + 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"); + spdk_iscsi_tgt_node_destruct(target); + return NULL; + } + + TAILQ_INIT(&target->pg_map_head); + rc = spdk_iscsi_tgt_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"); + spdk_iscsi_tgt_node_destruct(target); + 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_spdk_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_spdk_iscsi.MaxQueueDepth); + target->queue_depth = g_spdk_iscsi.MaxQueueDepth; + } + + rc = spdk_iscsi_tgt_node_register(target); + if (rc != 0) { + SPDK_ERRLOG("register target is failed\n"); + spdk_iscsi_tgt_node_destruct(target); + return NULL; + } + + return target; +} + +static int +spdk_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_spdk_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 = spdk_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 spdk_iscsi_parse_tgt_nodes(void) +{ + struct spdk_conf_section *sp; + int rc; + + SPDK_DEBUGLOG(SPDK_LOG_ISCSI, "spdk_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 = spdk_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 +spdk_iscsi_shutdown_tgt_nodes(void) +{ + struct spdk_iscsi_tgt_node *target, *tmp; + + pthread_mutex_lock(&g_spdk_iscsi.mutex); + TAILQ_FOREACH_SAFE(target, &g_spdk_iscsi.target_head, tailq, tmp) { + TAILQ_REMOVE(&g_spdk_iscsi.target_head, target, tailq); + spdk_iscsi_tgt_node_destruct(target); + } + pthread_mutex_unlock(&g_spdk_iscsi.mutex); +} + +int +spdk_iscsi_shutdown_tgt_node_by_name(const char *target_name) +{ + struct spdk_iscsi_tgt_node *target; + + pthread_mutex_lock(&g_spdk_iscsi.mutex); + target = spdk_iscsi_find_tgt_node(target_name); + if (target != NULL) { + spdk_iscsi_tgt_node_unregister(target); + spdk_iscsi_tgt_node_destruct(target); + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + + return 0; + } + pthread_mutex_unlock(&g_spdk_iscsi.mutex); + + return -ENOENT; +} + +int +spdk_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 = spdk_iscsi_task_get(conn, NULL, spdk_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; + + spdk_scsi_dev_queue_mgmt_task(target->dev, &task->scsi, SPDK_SCSI_TASK_FUNC_LUN_RESET); + } + + return 0; +} + +void spdk_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_spdk_iscsi.mutex); + TAILQ_FOREACH(target, &g_spdk_iscsi.target_head, tailq) { + if (portal_group) { + spdk_iscsi_tgt_node_delete_pg_map(target, portal_group); + } + if (initiator_group) { + spdk_iscsi_tgt_node_delete_ig_maps(target, initiator_group); + } + } + pthread_mutex_unlock(&g_spdk_iscsi.mutex); +} + +int +spdk_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 +spdk_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 (!spdk_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 +spdk_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_spdk_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 +spdk_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) { + 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 +spdk_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", "construct_target_node"); + + spdk_json_write_name(w, "params"); + spdk_iscsi_tgt_node_info_json(target, w); + + spdk_json_write_object_end(w); +} + +void +spdk_iscsi_tgt_nodes_info_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_tgt_node *target; + + TAILQ_FOREACH(target, &g_spdk_iscsi.target_head, tailq) { + spdk_iscsi_tgt_node_info_json(target, w); + } +} + +void +spdk_iscsi_tgt_nodes_config_json(struct spdk_json_write_ctx *w) +{ + struct spdk_iscsi_tgt_node *target; + + TAILQ_FOREACH(target, &g_spdk_iscsi.target_head, tailq) { + spdk_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 00000000..1d54922a --- /dev/null +++ b/src/spdk/lib/iscsi/tgt_node.h @@ -0,0 +1,146 @@ +/*- + * 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 "spdk/scsi.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 + +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; + char *alias; + + 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; + int lcore; + + int num_pg_maps; + TAILQ_HEAD(, spdk_iscsi_pg_map) pg_map_head; + TAILQ_ENTRY(spdk_iscsi_tgt_node) tailq; +}; + +int spdk_iscsi_parse_tgt_nodes(void); + +void spdk_iscsi_shutdown_tgt_nodes(void); +int spdk_iscsi_shutdown_tgt_node_by_name(const char *target_name); +int spdk_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); + +/* This typedef exists to work around an astyle 2.05 bug. + * Remove it when astyle is fixed. + */ +typedef struct spdk_iscsi_tgt_node _spdk_iscsi_tgt_node; + +/* + * 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. + */ +_spdk_iscsi_tgt_node * +spdk_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 spdk_iscsi_check_chap_params(bool disable, bool require, bool mutual, int group); + +int spdk_iscsi_tgt_node_add_pg_ig_maps(struct spdk_iscsi_tgt_node *target, + int *pg_tag_list, int *ig_tag_list, + uint16_t num_maps); +int spdk_iscsi_tgt_node_delete_pg_ig_maps(struct spdk_iscsi_tgt_node *target, + int *pg_tag_list, int *ig_tag_list, + uint16_t num_maps); + +bool spdk_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 *spdk_iscsi_find_tgt_node(const char *target_name); +int spdk_iscsi_tgt_node_reset(struct spdk_iscsi_tgt_node *target, + uint64_t lun); +int spdk_iscsi_tgt_node_cleanup_luns(struct spdk_iscsi_conn *conn, + struct spdk_iscsi_tgt_node *target); +void spdk_iscsi_tgt_node_delete_map(struct spdk_iscsi_portal_grp *portal_group, + struct spdk_iscsi_init_grp *initiator_group); +int spdk_iscsi_tgt_node_add_lun(struct spdk_iscsi_tgt_node *target, + const char *bdev_name, int lun_id); +int spdk_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 spdk_iscsi_tgt_nodes_config_text(FILE *fp); +void spdk_iscsi_tgt_nodes_info_json(struct spdk_json_write_ctx *w); +void spdk_iscsi_tgt_nodes_config_json(struct spdk_json_write_ctx *w); +#endif /* SPDK_ISCSI_TGT_NODE_H_ */ |