From a6d42e7d71324c5193c3b94d57d96ba2925d52e1 Mon Sep 17 00:00:00 2001 From: Peter Dunlap Date: Thu, 30 Oct 2008 15:49:07 -0600 Subject: [PATCH] PSARC 2008/587 iSCSI Port Provider for COMSTAR 6702584 Need iSCSI port provider for COMSTAR --- usr/src/Makefile.lint | 3 + usr/src/cmd/Makefile | 3 + usr/src/cmd/Makefile.cmd | 1 + usr/src/cmd/iscsitsvc/Makefile | 72 + usr/src/cmd/iscsitsvc/iscsi-target.xml | 110 + usr/src/cmd/iscsitsvc/iscsitsvc.c | 249 ++ usr/src/cmd/itadm/Makefile | 43 + usr/src/cmd/itadm/itadm.c | 2093 ++++++++++++++ usr/src/cmd/mdb/Makefile.common | 1 + usr/src/cmd/mdb/common/modules/idm/idm.c | 2480 ++++++++++++++++ usr/src/cmd/mdb/intel/amd64/idm/Makefile | 38 + usr/src/cmd/mdb/intel/ia32/idm/Makefile | 37 + usr/src/cmd/mdb/sparc/v9/idm/Makefile | 38 + usr/src/common/iscsi/base64.c | 255 ++ usr/src/common/iscsit/iscsit_common.c | 1590 +++++++++++ usr/src/lib/Makefile | 3 + usr/src/lib/libiscsit/Makefile | 56 + usr/src/lib/libiscsit/Makefile.com | 66 + usr/src/lib/libiscsit/amd64/Makefile | 32 + usr/src/lib/libiscsit/common/libiscsit.c | 1889 ++++++++++++ usr/src/lib/libiscsit/common/libiscsit.h | 747 +++++ usr/src/lib/libiscsit/common/llib-liscsit | 30 + usr/src/lib/libiscsit/common/mapfile-vers | 79 + usr/src/lib/libiscsit/i386/Makefile | 31 + usr/src/lib/libiscsit/sparc/Makefile | 29 + usr/src/lib/libiscsit/sparcv9/Makefile | 32 + usr/src/pkgdefs/Makefile | 4 + usr/src/pkgdefs/SUNWiscsidmr/Makefile | 37 + usr/src/pkgdefs/SUNWiscsidmr/pkginfo.tmpl | 49 + usr/src/pkgdefs/SUNWiscsidmr/prototype_com | 46 + usr/src/pkgdefs/SUNWiscsidmr/prototype_i386 | 54 + usr/src/pkgdefs/SUNWiscsidmr/prototype_sparc | 51 + usr/src/pkgdefs/SUNWiscsidmu/Makefile | 35 + usr/src/pkgdefs/SUNWiscsidmu/depend | 50 + usr/src/pkgdefs/SUNWiscsidmu/pkginfo.tmpl | 49 + usr/src/pkgdefs/SUNWiscsidmu/prototype_com | 47 + usr/src/pkgdefs/SUNWiscsidmu/prototype_i386 | 51 + usr/src/pkgdefs/SUNWiscsidmu/prototype_sparc | 49 + usr/src/pkgdefs/SUNWiscsitr/Makefile | 37 + usr/src/pkgdefs/SUNWiscsitr/depend | 50 + usr/src/pkgdefs/SUNWiscsitr/pkginfo.tmpl | 48 + usr/src/pkgdefs/SUNWiscsitr/postinstall | 40 + usr/src/pkgdefs/SUNWiscsitr/preremove | 36 + usr/src/pkgdefs/SUNWiscsitr/prototype_com | 60 + usr/src/pkgdefs/SUNWiscsitr/prototype_i386 | 51 + usr/src/pkgdefs/SUNWiscsitr/prototype_sparc | 49 + usr/src/pkgdefs/SUNWiscsitu/Makefile | 35 + usr/src/pkgdefs/SUNWiscsitu/depend | 49 + usr/src/pkgdefs/SUNWiscsitu/pkginfo.tmpl | 48 + usr/src/pkgdefs/SUNWiscsitu/prototype_com | 54 + usr/src/pkgdefs/SUNWiscsitu/prototype_i386 | 51 + usr/src/pkgdefs/SUNWiscsitu/prototype_sparc | 51 + usr/src/pkgdefs/etc/exception_list_i386 | 15 + usr/src/pkgdefs/etc/exception_list_sparc | 15 + usr/src/uts/common/Makefile.files | 13 + usr/src/uts/common/Makefile.rules | 28 + .../common/io/comstar/port/iscsit/iscsit.c | 2480 ++++++++++++++++ .../common/io/comstar/port/iscsit/iscsit.conf | 27 + .../common/io/comstar/port/iscsit/iscsit.h | 799 ++++++ .../io/comstar/port/iscsit/iscsit_auth.c | 751 +++++ .../io/comstar/port/iscsit/iscsit_auth.h | 34 + .../comstar/port/iscsit/iscsit_authclient.c | 261 ++ .../comstar/port/iscsit/iscsit_authclient.h | 160 ++ .../io/comstar/port/iscsit/iscsit_isns.c | 2531 ++++++++++++++++ .../io/comstar/port/iscsit/iscsit_isns.h | 109 + .../io/comstar/port/iscsit/iscsit_login.c | 2534 +++++++++++++++++ .../comstar/port/iscsit/iscsit_radiusauth.c | 183 ++ .../comstar/port/iscsit/iscsit_radiuspacket.c | 390 +++ .../io/comstar/port/iscsit/iscsit_sess.c | 792 ++++++ .../io/comstar/port/iscsit/iscsit_text.c | 324 +++ .../io/comstar/port/iscsit/iscsit_tgt.c | 2054 +++++++++++++ .../io/comstar/port/iscsit/radius_auth.h | 71 + usr/src/uts/common/io/idm/idm.c | 2131 ++++++++++++++ usr/src/uts/common/io/idm/idm_conn_sm.c | 1480 ++++++++++ usr/src/uts/common/io/idm/idm_impl.c | 1023 +++++++ usr/src/uts/common/io/idm/idm_so.c | 2525 ++++++++++++++++ usr/src/uts/common/io/idm/idm_text.c | 1709 +++++++++++ usr/src/uts/common/sys/Makefile | 25 + usr/src/uts/common/sys/Makefile.syshdrs | 11 + usr/src/uts/common/sys/idm/idm.h | 445 +++ usr/src/uts/common/sys/idm/idm_conn_sm.h | 269 ++ usr/src/uts/common/sys/idm/idm_impl.h | 479 ++++ usr/src/uts/common/sys/idm/idm_so.h | 110 + usr/src/uts/common/sys/idm/idm_text.h | 198 ++ usr/src/uts/common/sys/idm/idm_transport.h | 221 ++ usr/src/uts/common/sys/iscsi_protocol.h | 45 +- usr/src/uts/common/sys/iscsit/chap.h | 94 + usr/src/uts/common/sys/iscsit/iscsi_if.h | 651 +++++ usr/src/uts/common/sys/iscsit/iscsit_common.h | 292 ++ usr/src/uts/common/sys/iscsit/isns_protocol.h | 281 ++ usr/src/uts/common/sys/iscsit/radius_packet.h | 98 + .../uts/common/sys/iscsit/radius_protocol.h | 83 + usr/src/uts/intel/Makefile.intel.shared | 2 + usr/src/uts/intel/idm/Makefile | 89 + usr/src/uts/intel/iscsit/Makefile | 96 + usr/src/uts/sparc/Makefile.sparc.shared | 2 + usr/src/uts/sparc/idm/Makefile | 87 + usr/src/uts/sparc/iscsit/Makefile | 96 + 98 files changed, 37293 insertions(+), 8 deletions(-) create mode 100644 usr/src/cmd/iscsitsvc/Makefile create mode 100644 usr/src/cmd/iscsitsvc/iscsi-target.xml create mode 100644 usr/src/cmd/iscsitsvc/iscsitsvc.c create mode 100644 usr/src/cmd/itadm/Makefile create mode 100644 usr/src/cmd/itadm/itadm.c create mode 100644 usr/src/cmd/mdb/common/modules/idm/idm.c create mode 100644 usr/src/cmd/mdb/intel/amd64/idm/Makefile create mode 100644 usr/src/cmd/mdb/intel/ia32/idm/Makefile create mode 100644 usr/src/cmd/mdb/sparc/v9/idm/Makefile create mode 100644 usr/src/common/iscsi/base64.c create mode 100644 usr/src/common/iscsit/iscsit_common.c create mode 100644 usr/src/lib/libiscsit/Makefile create mode 100644 usr/src/lib/libiscsit/Makefile.com create mode 100644 usr/src/lib/libiscsit/amd64/Makefile create mode 100644 usr/src/lib/libiscsit/common/libiscsit.c create mode 100644 usr/src/lib/libiscsit/common/libiscsit.h create mode 100644 usr/src/lib/libiscsit/common/llib-liscsit create mode 100644 usr/src/lib/libiscsit/common/mapfile-vers create mode 100644 usr/src/lib/libiscsit/i386/Makefile create mode 100644 usr/src/lib/libiscsit/sparc/Makefile create mode 100644 usr/src/lib/libiscsit/sparcv9/Makefile create mode 100644 usr/src/pkgdefs/SUNWiscsidmr/Makefile create mode 100644 usr/src/pkgdefs/SUNWiscsidmr/pkginfo.tmpl create mode 100644 usr/src/pkgdefs/SUNWiscsidmr/prototype_com create mode 100644 usr/src/pkgdefs/SUNWiscsidmr/prototype_i386 create mode 100644 usr/src/pkgdefs/SUNWiscsidmr/prototype_sparc create mode 100644 usr/src/pkgdefs/SUNWiscsidmu/Makefile create mode 100644 usr/src/pkgdefs/SUNWiscsidmu/depend create mode 100644 usr/src/pkgdefs/SUNWiscsidmu/pkginfo.tmpl create mode 100644 usr/src/pkgdefs/SUNWiscsidmu/prototype_com create mode 100644 usr/src/pkgdefs/SUNWiscsidmu/prototype_i386 create mode 100644 usr/src/pkgdefs/SUNWiscsidmu/prototype_sparc create mode 100644 usr/src/pkgdefs/SUNWiscsitr/Makefile create mode 100644 usr/src/pkgdefs/SUNWiscsitr/depend create mode 100644 usr/src/pkgdefs/SUNWiscsitr/pkginfo.tmpl create mode 100644 usr/src/pkgdefs/SUNWiscsitr/postinstall create mode 100644 usr/src/pkgdefs/SUNWiscsitr/preremove create mode 100644 usr/src/pkgdefs/SUNWiscsitr/prototype_com create mode 100644 usr/src/pkgdefs/SUNWiscsitr/prototype_i386 create mode 100644 usr/src/pkgdefs/SUNWiscsitr/prototype_sparc create mode 100644 usr/src/pkgdefs/SUNWiscsitu/Makefile create mode 100644 usr/src/pkgdefs/SUNWiscsitu/depend create mode 100644 usr/src/pkgdefs/SUNWiscsitu/pkginfo.tmpl create mode 100644 usr/src/pkgdefs/SUNWiscsitu/prototype_com create mode 100644 usr/src/pkgdefs/SUNWiscsitu/prototype_i386 create mode 100644 usr/src/pkgdefs/SUNWiscsitu/prototype_sparc create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit.c create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit.conf create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit.h create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.c create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.h create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.c create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.h create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.c create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.h create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit_login.c create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiusauth.c create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiuspacket.c create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit_sess.c create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit_text.c create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/iscsit_tgt.c create mode 100644 usr/src/uts/common/io/comstar/port/iscsit/radius_auth.h create mode 100644 usr/src/uts/common/io/idm/idm.c create mode 100644 usr/src/uts/common/io/idm/idm_conn_sm.c create mode 100644 usr/src/uts/common/io/idm/idm_impl.c create mode 100644 usr/src/uts/common/io/idm/idm_so.c create mode 100644 usr/src/uts/common/io/idm/idm_text.c create mode 100644 usr/src/uts/common/sys/idm/idm.h create mode 100644 usr/src/uts/common/sys/idm/idm_conn_sm.h create mode 100644 usr/src/uts/common/sys/idm/idm_impl.h create mode 100644 usr/src/uts/common/sys/idm/idm_so.h create mode 100644 usr/src/uts/common/sys/idm/idm_text.h create mode 100644 usr/src/uts/common/sys/idm/idm_transport.h create mode 100644 usr/src/uts/common/sys/iscsit/chap.h create mode 100644 usr/src/uts/common/sys/iscsit/iscsi_if.h create mode 100644 usr/src/uts/common/sys/iscsit/iscsit_common.h create mode 100644 usr/src/uts/common/sys/iscsit/isns_protocol.h create mode 100644 usr/src/uts/common/sys/iscsit/radius_packet.h create mode 100644 usr/src/uts/common/sys/iscsit/radius_protocol.h create mode 100644 usr/src/uts/intel/idm/Makefile create mode 100644 usr/src/uts/intel/iscsit/Makefile create mode 100644 usr/src/uts/sparc/idm/Makefile create mode 100644 usr/src/uts/sparc/iscsit/Makefile diff --git a/usr/src/Makefile.lint b/usr/src/Makefile.lint index 59fd87161f78..6cffcc986f17 100644 --- a/usr/src/Makefile.lint +++ b/usr/src/Makefile.lint @@ -166,7 +166,9 @@ COMMON_SUBDIRS = \ cmd/iscsi \ cmd/iscsiadm \ cmd/iscsid \ + cmd/iscsitsvc \ cmd/isns \ + cmd/itadm \ cmd/join \ cmd/kbd \ cmd/killall \ @@ -364,6 +366,7 @@ COMMON_SUBDIRS = \ lib/libipmp \ lib/libipp \ lib/libipsecutil \ + lib/libiscsit \ lib/libiscsitgt \ lib/libkmf \ lib/libkstat \ diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile index af2b9c563e13..927f5ca801cd 100644 --- a/usr/src/cmd/Makefile +++ b/usr/src/cmd/Makefile @@ -213,7 +213,9 @@ COMMON_SUBDIRS= \ itutools \ iscsiadm \ iscsid \ + iscsitsvc \ isns \ + itadm \ java \ join \ kbd \ @@ -604,6 +606,7 @@ MSGSUBDIRS= \ iscsiadm \ iscsid \ isns \ + itadm \ join \ kbd \ krb5 \ diff --git a/usr/src/cmd/Makefile.cmd b/usr/src/cmd/Makefile.cmd index eb2ef79e2a82..ca394186fffb 100644 --- a/usr/src/cmd/Makefile.cmd +++ b/usr/src/cmd/Makefile.cmd @@ -235,6 +235,7 @@ ROOTSVCSYSTEMFILESYSTEM= $(ROOTSVCSYSTEM)/filesystem ROOTSVCSYSTEMSECURITY= $(ROOTSVCSYSTEM)/security ROOTSVCNETWORK= $(ROOTVARSVCMANIFEST)/network ROOTSVCNETWORKDNS= $(ROOTSVCNETWORK)/dns +ROOTSVCNETWORKISCSI= $(ROOTSVCNETWORK)/iscsi ROOTSVCNETWORKLDAP= $(ROOTSVCNETWORK)/ldap ROOTSVCNETWORKNFS= $(ROOTSVCNETWORK)/nfs ROOTSVCNETWORKNIS= $(ROOTSVCNETWORK)/nis diff --git a/usr/src/cmd/iscsitsvc/Makefile b/usr/src/cmd/iscsitsvc/Makefile new file mode 100644 index 000000000000..953ff80c77d1 --- /dev/null +++ b/usr/src/cmd/iscsitsvc/Makefile @@ -0,0 +1,72 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +PROG = iscsi-target + +include ../Makefile.cmd + +COMMONBASE = ../../common + +LOCAL_OBJS = iscsitsvc.o +COMMON_OBJS = cmdparse.o +LOCAL_SRCS = $(LOCAL_OBJS:%.o=%.c) +COMMON_SRCS = $(COMMON_OBJS:%.o=$(COMMONBASE)/cmdparse/%.c) +OBJS = $(LOCAL_OBJS) $(COMMON_OBJS) +SRCS = $(LOCAL_SRCS) $(COMMON_SRCS) + +CPPFLAGS += -I$(COMMONBASE)/cmdparse + +MANIFEST = iscsi-target.xml +SVCMETHOD = iscsi-target + +ROOTMANIFESTDIR = $(ROOTSVCNETWORKISCSI) +$(ROOTSVCNETWORKISCSI)/iscsi-target.xml := OWNER = root +$(ROOTSVCNETWORKISCSI)/iscsi-target.xml := GROUP = bin +$(ROOTSVCNETWORKISCSI)/iscsi-target.xml := FILEMODE = 0444 + +.KEEP_STATE: + +all: $(PROG) + +$(PROG): $(OBJS) + $(LINK.c) -o $@ $(OBJS) $(LDLIBS) + $(POST_PROCESS) + +install: all $(ROOTMANIFEST) $(ROOTSVCMETHOD) + +check: $(CHKMANIFEST) + $(CSTYLE) -pPc $(SRCS:%=%) + +cmdparse.o: $(COMMONBASE)/cmdparse/cmdparse.c + $(COMPILE.c) -o $@ $(COMMONBASE)/cmdparse/cmdparse.c + $(POST_PROCESS_O) + +clean: + $(RM) $(OBJS) + +lint: lint_SRCS + +include ../Makefile.targ diff --git a/usr/src/cmd/iscsitsvc/iscsi-target.xml b/usr/src/cmd/iscsitsvc/iscsi-target.xml new file mode 100644 index 000000000000..4598d273806f --- /dev/null +++ b/usr/src/cmd/iscsitsvc/iscsi-target.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/usr/src/cmd/iscsitsvc/iscsitsvc.c b/usr/src/cmd/iscsitsvc/iscsitsvc.c new file mode 100644 index 000000000000..0c147d2d4e91 --- /dev/null +++ b/usr/src/cmd/iscsitsvc/iscsitsvc.c @@ -0,0 +1,249 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static int it_enable(int, char **, cmdOptions_t *, void *); +static int it_disable(int, char **, cmdOptions_t *, void *); + +/* + * MAJOR - This should only change when there is an incompatible change made + * to the interfaces or the output. + * + * MINOR - This should change whenever there is a new command or new feature + * with no incompatible change. + */ +#define VERSION_STRING_MAJOR "1" +#define VERSION_STRING_MINOR "0" +#define VERSION_STRING_MAX_LEN 10 + +/* 10 ms sleep in nanoseconds */ +#define TEN_MS_NANOSLEEP 10000000 + +/* tables set up based on cmdparse instructions */ + +/* add new options here */ +optionTbl_t longOptions[] = { + {NULL, 0, 0, 0} +}; + +/* + * Add new subcommands here + */ +subCommandProps_t subcommands[] = { + {"start", it_enable, NULL, NULL, NULL, OPERAND_NONE, NULL}, + {"stop", it_disable, NULL, NULL, NULL, OPERAND_NONE, NULL}, + {NULL, 0, NULL, NULL, 0, NULL, 0, NULL} +}; + +/* globals */ +char *cmdName; + +/* + * Opens the iSCSI Target Node + * + * fd - Return the iscsit file descriptor + */ +static int +it_open(int *fd) +{ + + int ret = ITADM_SUCCESS; + + *fd = open(ISCSIT_NODE, O_RDONLY); + if (*fd < 0) { + if (errno == EPERM) { + (void) fprintf(stdout, "open failed: EPERM"); + ret = ITADM_PERM; + } else { + (void) fprintf(stdout, "open failed: INVALID"); + ret = ITADM_INVALID; + } + } + + return (ret); +} + +/* + * Enables the iSCSI Target + */ +/*ARGSUSED*/ +static int +it_enable(int operandLen, char *operands[], cmdOptions_t *options, + void *args) +{ + int ret; + int fd; + char buf[256]; + uint32_t *buflenp; + char *fqhnp; + iscsit_hostinfo_t hostinfo; + + (void) fprintf(stdout, "%s: %s\n", cmdName, + gettext("Requesting to enable iscsi target")); + + bzero(buf, 256); + bzero(hostinfo.fqhn, sizeof (hostinfo.fqhn)); + + /* Open the iscsi target node */ + if ((ret = it_open(&fd)) != ITADM_SUCCESS) { + (void) fprintf(stdout, "Unable to open device %s", ISCSIT_NODE); + return (ret); + } + + (void) fprintf(stdout, "it_enable [fd=%d]\n", fd); + /* enable the iscsi target */ + buflenp = (uint32_t *)((void *)&buf); + *buflenp = strlen("target_name") + 1; + (void) strncpy(buf + sizeof (uint32_t), "target_name", + 256 - sizeof (uint32_t)); + + fqhnp = &hostinfo.fqhn[0]; + + ret = sysinfo(SI_HOSTNAME, fqhnp, 256); + + if ((ret != -1) && (ret < sizeof (hostinfo.fqhn))) { + fqhnp += ret; + hostinfo.length = ret; + hostinfo.fqhn[ret-1] = '.'; + hostinfo.length += sysinfo(SI_SRPC_DOMAIN, fqhnp, + sizeof (hostinfo.fqhn) - ret); + } + + (void) fprintf(stdout, "it_enable: fqhn = '%s'\n", hostinfo.fqhn); + + if ((ret = ioctl(fd, ISCSIT_IOC_ENABLE_SVC, &hostinfo)) != 0) { + (void) fprintf(stdout, "Unable to issue ioctl: %d", errno); + return (ret); + } + return (ITADM_SUCCESS); +} + + +/* + * Disable the iSCSI target + */ +/* ARGSUSED */ +static int +it_disable(int operandLen, char *operands[], cmdOptions_t *options, + void *args) +{ + int ret; + int fd; + + (void) fprintf(stdout, "%s: %s\n", cmdName, + gettext("Requesting to disable iscsi target")); + + /* Open the iscsi target node */ + if ((ret = it_open(&fd)) != ITADM_SUCCESS) { + return (ret); + } + + /* disable the iSCSI target */ + if ((ret = ioctl(fd, ISCSIT_IOC_DISABLE_SVC, NULL)) != 0) { + return (ret); + } + return (ITADM_SUCCESS); +} + +/* + * input: + * execFullName - exec name of program (argv[0]) + * + * copied from usr/src/cmd/zoneadm/zoneadm.c in OS/Net + * (changed name to lowerCamelCase to keep consistent with this file) + * + * Returns: + * command name portion of execFullName + */ +static char * +getExecBasename(char *execFullname) +{ + char *lastSlash, *execBasename; + + /* guard against '/' at end of command invocation */ + for (;;) { + lastSlash = strrchr(execFullname, '/'); + if (lastSlash == NULL) { + execBasename = execFullname; + break; + } else { + execBasename = lastSlash + 1; + if (*execBasename == '\0') { + *lastSlash = '\0'; + continue; + } + break; + } + } + return (execBasename); +} + +int +main(int argc, char *argv[]) +{ + synTables_t synTables; + char versionString[VERSION_STRING_MAX_LEN]; + int ret; + int funcRet; + void *subcommandArgs = NULL; + + (void) setlocale(LC_ALL, ""); + /* set global command name */ + cmdName = getExecBasename(argv[0]); + + (void) snprintf(versionString, VERSION_STRING_MAX_LEN, "%s.%s", + VERSION_STRING_MAJOR, VERSION_STRING_MINOR); + synTables.versionString = versionString; + synTables.longOptionTbl = &longOptions[0]; + synTables.subCommandPropsTbl = &subcommands[0]; + + ret = cmdParse(argc, argv, synTables, subcommandArgs, &funcRet); + if (ret != 0) { + return (ret); + } + + return (funcRet); +} /* end main */ diff --git a/usr/src/cmd/itadm/Makefile b/usr/src/cmd/itadm/Makefile new file mode 100644 index 000000000000..c15a27120983 --- /dev/null +++ b/usr/src/cmd/itadm/Makefile @@ -0,0 +1,43 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +PROG= itadm + +include ../Makefile.cmd + +LDLIBS += -lgen -lsecdb -lscf -lstmf -lnvpair -liscsit + +.KEEP_STATE: + +all: $(PROG) + +install: all $(ROOTUSRSBINPROG) + +clean: + +lint: lint_PROG + +include ../Makefile.targ diff --git a/usr/src/cmd/itadm/itadm.c b/usr/src/cmd/itadm/itadm.c new file mode 100644 index 000000000000..60f31d3a0b93 --- /dev/null +++ b/usr/src/cmd/itadm/itadm.c @@ -0,0 +1,2093 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* what's this used for?? */ +#define ITADM_VERSION "1.0" + +/* SMF service info */ +#define ISCSIT_SVC "svc:/network/iscsi/target:default" + +#define STMF_STALE(ret) {\ + if (ret == STMF_ERROR_PROV_DATA_STALE) {\ + (void) fprintf(stderr, "%s\n",\ + gettext("Configuration changed during processing. "\ + "Check the configuration, then retry this command "\ + "if appropriate."));\ + }\ +} + +#define ITADM_CHKAUTH(sec) {\ + if (!chkauthattr(sec, itadm_uname)) {\ + (void) fprintf(stderr,\ + gettext("Error, operation requires authorization %s"),\ + sec);\ + (void) fprintf(stderr, "\n");\ + return (1);\ + }\ +} + +static struct option itadm_long[] = { + {"alias", required_argument, NULL, 'l'}, + {"auth-method", required_argument, NULL, 'a'}, + {"chap-secret", no_argument, NULL, 's'}, + {"chap-secret-file", required_argument, NULL, 'S'}, + {"chap-user", required_argument, NULL, 'u'}, + {"force", no_argument, NULL, 'f'}, + {"help", no_argument, NULL, 'h'}, + {"help", no_argument, NULL, '?'}, + {"isns", required_argument, NULL, 'i'}, + {"isns-server", required_argument, NULL, 'I'}, + {"node-name", required_argument, NULL, 'n'}, + {"radius-secret", no_argument, NULL, 'd'}, + {"radius-secret-file", required_argument, NULL, 'D'}, + {"radius-server", required_argument, NULL, 'r'}, + {"tpg-tag", required_argument, NULL, 't'}, + {"verbose", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {NULL, 0, NULL, 0} +}; + +char c_tgt[] = "itadm create-target [-a radius|chap|none|default] [-s] \ +[-S chap-secret-path] [-u chap-username] [-n target-node-name] \ +[-l alias] [-t tpg-name[,tpg-name,...]]"; + +static char m_tgt[] = "itadm modify-target [-a radius|chap|none|default] [-s] \ +[-S chap-secret-path] [-u chap-username] [-n new-target-node-name] \ +[-l alias] [-t tpg-name[,tpg-name,...]] target-node-name"; + +static char d_tgt[] = "itadm delete-target [-f] target-node-name"; + +static char l_tgt[] = "itadm list-target [-v] [target-node-name"; + +static char c_tpg[] = "itadm create-tpg tpg-name IP-address[:port] \ +[IP-address[:port]] [...]"; + +static char l_tpg[] = "itadm list-tpg [-v] [tpg-name]"; + +static char d_tpg[] = "itadm delete-tpg [-f] tpg-name"; + +static char c_ini[] = "itadm create-initiator [-s] [-S chap-secret-path] \ +[-u chap-username] initiator-node-name"; + +static char m_ini[] = "itadm modify-initiator [-s] [-S chap-secret-path] \ +[-u chap-username] initiator-node-name"; + +static char l_ini[] = "itadm list-initiator [-v] initiator-node-name"; + +static char d_ini[] = "itadm delete-inititator initiator-node-name"; + +static char m_def[] = "itadm modify-defaults [-a radius|chap|none] \ +[-r IP-address[:port]] [-d] [-D radius-secret-path] [-i enable|disable] \ +[-I IP-address[:port][,IP-adddress[:port]]]"; + +static char l_def[] = "itadm list-defaults"; + + +/* keep the order of this enum in the same order as the 'subcmds' struct */ +typedef enum { + CREATE_TGT, + MODIFY_TGT, + DELETE_TGT, + LIST_TGT, + CREATE_TPG, + DELETE_TPG, + LIST_TPG, + CREATE_INI, + MODIFY_INI, + LIST_INI, + DELETE_INI, + MODIFY_DEF, + LIST_DEF, + NULL_SUBCMD /* must always be last! */ +} itadm_sub_t; + +typedef struct { + char *name; + char *shortopts; + char *usemsg; +} itadm_subcmds_t; + +static itadm_subcmds_t subcmds[] = { + {"create-target", ":a:sS:u:n:l:t:h?", c_tgt}, + {"modify-target", ":a:sS:u:n:l:t:h?", m_tgt}, + {"delete-target", ":fh?", d_tgt}, + {"list-target", ":vh?", l_tgt}, + {"create-tpg", ":h?", c_tpg}, + {"delete-tpg", ":fh?", d_tpg}, + {"list-tpg", ":vh?", l_tpg}, + {"create-initiator", ":sS:u:h?", c_ini}, + {"modify-initiator", ":sS:u:h?", m_ini}, + {"list-initiator", ":vh?", l_ini}, + {"delete-initiator", ":h?", d_ini}, + {"modify-defaults", ":a:r:dD:i:I:h?", m_def}, + {"list-defaults", ":h?", l_def}, + {NULL, ":h?", NULL}, +}; + +/* used for checking if user is authorized */ +static char *itadm_uname = NULL; + +/* prototypes */ +static int +itadm_get_password(nvlist_t *nvl, char *key, char *passfile, + char *phrase); + +static int +itadm_opt_to_arr(nvlist_t *nvl, char *key, char *opt, uint32_t *num); + +static int +create_target(char *tgt, nvlist_t *proplist); + +static int +modify_target(char *tgt, char *new, nvlist_t *proplist); + +static int +delete_target(char *tgt, boolean_t force); + +static int +list_target(char *tgt, boolean_t verbose, boolean_t script); + +static int +create_tpg(char *tpg, int addrc, char **addrs); + +static int +list_tpg(char *tpg, boolean_t verbose, boolean_t script); + +static int +delete_tpg(char *tpg, boolean_t force); + +static int +modify_initiator(char *ini, nvlist_t *proplist, boolean_t create); + +static int +list_initiator(char *ini, boolean_t verbose, boolean_t script); + +static int +delete_initiator(char *ini); + +static int +modify_defaults(nvlist_t *proplist); + +static int +list_defaults(boolean_t script); + +static void +tag_name_to_num(char *tagname, uint16_t *tagnum); + +/* prototype from iscsit_common.h */ +extern int +sockaddr_to_str(struct sockaddr_storage *sa, char **addr); + +int +main(int argc, char *argv[]) +{ + int ret = 0; + int idx = NULL_SUBCMD; + char c; + int newargc = argc; + char **newargv = NULL; + char *objp; + int itind = 0; + nvlist_t *proplist = NULL; + boolean_t verbose = B_FALSE; + boolean_t scripting = B_FALSE; + boolean_t tbool; + char *targetname = NULL; + char *propname; + boolean_t force = B_FALSE; + struct passwd *pwd = NULL; + uint32_t count = 0; + char *smfstate = NULL; + + (void) setlocale(LC_ALL, ""); + (void) textdomain(TEXT_DOMAIN); + + if (argc < 2) { + ret = 1; + goto usage_error; + } + + for (idx = 0; subcmds[idx].name != NULL; idx++) { + if (strcmp(argv[1], subcmds[idx].name) == 0) { + break; + } + } + + + /* get the caller's user name for subsequent chkauthattr() calls */ + pwd = getpwuid(getuid()); + if (pwd == NULL) { + (void) fprintf(stderr, "%s\n", + gettext("Could not determine callers user name.")); + return (1); + } + + itadm_uname = strdup(pwd->pw_name); + + /* increment past command & subcommand */ + newargc--; + newargv = &(argv[1]); + + ret = nvlist_alloc(&proplist, NV_UNIQUE_NAME, 0); + if (ret != 0) { + ret = errno; + (void) fprintf(stderr, + gettext("Could not allocate nvlist, errno = %d"), + ret); + (void) fprintf(stderr, "\n"); + ret = 1; + goto usage_error; + } + + while ((ret == 0) && (newargv)) { + c = getopt_long(newargc, newargv, subcmds[idx].shortopts, + itadm_long, &itind); + if (c == -1) { + break; + } + + switch (c) { + case 0: + /* flag set by getopt */ + break; + case 'a': + ret = nvlist_add_string(proplist, + "auth", optarg); + break; + case 'd': + ret = itadm_get_password(proplist, + "radiussecret", NULL, + gettext("Enter RADIUS secret: ")); + break; + case 'D': + ret = itadm_get_password(proplist, + "radiussecret", optarg, NULL); + break; + case 'f': + force = B_TRUE; + break; + case '?': + /* + * '?' is returned for both unrecognized + * options and if explicitly provided on + * the command line. The latter should + * be handled the same as -h. + */ + if (strcmp(newargv[optind-1], "-?") != 0) { + (void) fprintf(stderr, + gettext("Unrecognized option %s"), + newargv[optind-1]); + (void) fprintf(stderr, "\n"); + ret = 1; + } + goto usage_error; + case 'h': + goto usage_error; + case 'i': + if (strncmp(optarg, "enable", strlen(optarg)) + == 0) { + tbool = B_TRUE; + } else if (strncmp(optarg, "disable", + strlen(optarg)) == 0) { + tbool = B_FALSE; + } else { + (void) fprintf(stderr, "%s\n", + gettext("invalid value for -i")); + ret = 1; + break; + } + ret = nvlist_add_boolean_value(proplist, + "isns", tbool); + break; + case 'I': + /* possibly multi-valued */ + ret = itadm_opt_to_arr(proplist, + "isnsserver", optarg, &count); + if ((ret == 0) && (count > 8)) { + (void) fprintf(stderr, "%s\n", + gettext( + "Too many iSNS servers specified, " + "maximum of 8 allowed")); + ret = 1; + } + break; + case 'l': + ret = nvlist_add_string(proplist, + "alias", optarg); + break; + case 'n': + targetname = strdup(optarg); + if (targetname == NULL) { + ret = ENOMEM; + } + break; + case 'r': + ret = nvlist_add_string(proplist, + "radiusserver", optarg); + break; + case 's': + if ((idx == CREATE_TGT) || + (idx == MODIFY_TGT)) { + propname = "targetchapsecret"; + } else { + propname = "chapsecret"; + } + ret = itadm_get_password(proplist, + propname, NULL, + gettext("Enter CHAP secret: ")); + break; + case 'S': + if ((idx == CREATE_TGT) || + (idx == MODIFY_TGT)) { + propname = "targetchapsecret"; + } else { + propname = "chapsecret"; + } + ret = itadm_get_password(proplist, + propname, optarg, NULL); + break; + case 't': + /* possibly multi-valued */ + ret = itadm_opt_to_arr(proplist, + "tpg-tag", optarg, NULL); + break; + case 'u': + if ((idx == CREATE_TGT) || + (idx == MODIFY_TGT)) { + propname = "targetchapuser"; + } else { + propname = "chapuser"; + } + ret = nvlist_add_string(proplist, + propname, optarg); + break; + case 'v': + verbose = B_TRUE; + break; + case ':': + (void) fprintf(stderr, + gettext("Option %s requires an operand."), + newargv[optind-1]); + (void) fprintf(stderr, "\n"); + + /* fall through to default */ + default: + ret = 1; + break; + } + } + + if (ret != 0) { + goto usage_error; + } + + /* after getopt() to allow handling of -h option */ + if ((itadm_sub_t)idx == NULL_SUBCMD) { + (void) fprintf(stderr, "%s\n", + gettext("Error, no subcommand specified")); + ret = 1; + goto usage_error; + } + + /* + * some subcommands take multiple operands, so adjust now that + * getopt is complete + */ + newargc -= optind; + if (newargc == 0) { + newargv = NULL; + objp = NULL; + } else { + newargv = &(newargv[optind]); + objp = newargv[0]; + } + + if (objp == NULL) { + switch ((itadm_sub_t)idx) { + case MODIFY_TGT: + case DELETE_TGT: + case CREATE_TPG: + case DELETE_TPG: + case CREATE_INI: + case MODIFY_INI: + case DELETE_INI: + /* These subcommands need operands */ + ret = 1; + goto usage_error; + default: + break; + } + } + + /* + * XXX - this should probably get pushed down to the library + * depending on the decision to allow/disallow configuratoin + * without the service running. + */ + /* + * Make sure iSCSI target service is enabled before + * proceeding. + */ + smfstate = smf_get_state(ISCSIT_SVC); + if (!smfstate || + (strcmp(smfstate, SCF_STATE_STRING_ONLINE) != 0)) { + (void) fprintf(stderr, "%s\n", + gettext("The iSCSI target service must be online " + "before running this command.")); + (void) fprintf(stderr, + gettext("Use 'svcadm enable -r %s'"), ISCSIT_SVC); + (void) fprintf(stderr, "\n"); + (void) fprintf(stderr, "%s\n", + gettext("to enable the service and its prerequisite " + "services and/or")); + (void) fprintf(stderr, + gettext("'svcs -x %s' to determine why it is not online."), + ISCSIT_SVC); + (void) fprintf(stderr, "\n"); + + return (1); + } + + switch ((itadm_sub_t)idx) { + case CREATE_TGT: + if (targetname) { + ret = create_target(targetname, proplist); + } else { + /* + * OK for objp to be NULL here. If the + * user did not specify a target name, + * one will be generated. + */ + ret = create_target(objp, proplist); + } + break; + case MODIFY_TGT: + ret = modify_target(objp, targetname, proplist); + break; + case DELETE_TGT: + ret = delete_target(objp, force); + break; + case LIST_TGT: + ret = list_target(objp, verbose, scripting); + break; + case CREATE_TPG: + ret = create_tpg(objp, newargc - 1, &(newargv[1])); + break; + case DELETE_TPG: + ret = delete_tpg(objp, force); + break; + case LIST_TPG: + ret = list_tpg(objp, verbose, scripting); + break; + case CREATE_INI: + ret = modify_initiator(objp, proplist, B_TRUE); + break; + case MODIFY_INI: + ret = modify_initiator(objp, proplist, B_FALSE); + break; + case LIST_INI: + ret = list_initiator(objp, verbose, scripting); + break; + case DELETE_INI: + ret = delete_initiator(objp); + break; + case MODIFY_DEF: + ret = modify_defaults(proplist); + break; + case LIST_DEF: + ret = list_defaults(scripting); + break; + default: + ret = 1; + goto usage_error; + } + + if (ret != 0) { + (void) fprintf(stderr, + gettext("itadm %s failed with error %d"), + subcmds[idx].name, ret); + (void) fprintf(stderr, "\n"); + } + return (ret); + +usage_error: + if (subcmds[idx].name) { + (void) printf("%s\n", gettext(subcmds[idx].usemsg)); + } else { + /* overall usage */ + (void) printf("%s\n\n", gettext("itadm usage:")); + for (idx = 0; subcmds[idx].name != NULL; idx++) { + if (!subcmds[idx].usemsg) { + continue; + } + (void) printf("\t%s\n", gettext(subcmds[idx].usemsg)); + } + } + + return (ret); +} + +static int +create_target(char *tgt, nvlist_t *proplist) +{ + int ret; + it_config_t *cfg = NULL; + it_tgt_t *tgtp; + char **tags = NULL; + uint32_t count = 0; + nvlist_t *errlist = NULL; + int i; + it_tpg_t *tpg = NULL; + uint16_t tagid = 0; + it_tpgt_t *tpgt; + char *sec = "solaris.smf.modify.stmf"; + + ITADM_CHKAUTH(sec); + + if (tgt) { + /* + * validate input name - what are the rules for EUI + * and IQN values? + */ + if ((strncmp(tgt, "eui.", 4) != 0) && + (strncmp(tgt, "iqn.", 4) != 0)) { + (void) fprintf(stderr, gettext("Invalid name %s"), + tgt); + (void) fprintf(stderr, "\n"); + return (EINVAL); + } + } + + ret = it_config_load(&cfg); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error retrieving iSCSI target configuration: %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + ret = it_tgt_create(cfg, &tgtp, tgt); + if (ret != 0) { + if (ret == EFAULT) { + (void) fprintf(stderr, + gettext("Invalid iSCSI name %s"), tgt); + } else if (ret == EEXIST) { + (void) fprintf(stderr, + gettext("iSCSI target %s already configured"), + tgt); + } else { + (void) fprintf(stderr, + gettext("Error creating target: %d"), ret); + } + (void) fprintf(stderr, "\n"); + goto done; + } + + /* set the target portal group tags */ + ret = nvlist_lookup_string_array(proplist, "tpg-tag", &tags, + &count); + + if (ret == ENOENT) { + /* none specified. is this ok? */ + ret = 0; + } else if (ret != 0) { + (void) fprintf(stderr, + gettext("internal error: %d"), ret); + (void) fprintf(stderr, "\n"); + goto done; + } + + /* special case, don't set any TPGs */ + if (tags && (strcmp("default", tags[0]) == 0)) { + count = 0; + } + + for (i = 0; i < count; i++) { + if (!tags[i]) { + continue; + } + + /* see that all referenced groups are already defined */ + tpg = cfg->config_tpg_list; + while (tpg != NULL) { + if (strcmp(tags[i], tpg->tpg_name) == 0) { + break; + } + + tpg = tpg->tpg_next; + } + if (tpg == NULL) { + (void) fprintf(stderr, + gettext("Invalid tpg-tag %s, tag not defined"), + tags[i]); + (void) fprintf(stderr, "\n"); + ret = 1; + goto done; + } + + /* generate the tag number to use */ + tag_name_to_num(tags[i], &tagid); + + ret = it_tpgt_create(cfg, tgtp, &tpgt, tags[i], tagid); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Could not add target portal group" + "tag %s, error %d"), tags[i], ret); + (void) fprintf(stderr, "\n"); + goto done; + } + tagid++; + } + + /* remove the tags from the proplist before continuing */ + if (tags) { + (void) nvlist_remove_all(proplist, "tpg-tag"); + } + + ret = it_tgt_setprop(cfg, tgtp, proplist, &errlist); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error setting target properties, %d"), ret); + (void) fprintf(stderr, "\n"); + if (errlist) { + nvpair_t *nvp = NULL; + char *nn; + char *nv; + + while ((nvp = nvlist_next_nvpair(errlist, nvp)) + != NULL) { + nv = NULL; + + nn = nvpair_name(nvp); + (void) nvpair_value_string(nvp, &nv); + + if (nv != NULL) { + (void) fprintf(stderr, "\t%s: %s\n", + nn, nv); + } + } + + nvlist_free(errlist); + } + goto done; + } + + if (ret == 0) { + ret = it_config_commit(cfg); + STMF_STALE(ret); + } + +done: + if (ret == 0) { + (void) printf(gettext("Target %s successfully created"), + tgtp->tgt_name); + (void) printf("\n"); + } + + it_config_free(cfg); + + return (ret); +} + +int +list_target(char *tgt, boolean_t verbose, boolean_t script) +{ + int ret; + it_config_t *cfg; + it_tgt_t *ptr; + boolean_t found = B_FALSE; + boolean_t first = B_TRUE; + boolean_t first_tag = B_TRUE; + char *gauth = "none"; + char *galias = "-"; + char *auth; + char *alias; + char *chapu; + char *chaps; + it_tpgt_t *tagp; + char *sec = "solaris.smf.read.stmf"; + stmfDevid devid; + stmfSessionList *sess = NULL; + stmfTargetProperties props; + char *state; + int num_sessions; + + ITADM_CHKAUTH(sec); + + ret = it_config_load(&cfg); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error retrieving iSCSI target configuration: %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + ptr = cfg->config_tgt_list; + + /* grab global defaults for auth, alias */ + if (cfg->config_global_properties) { + (void) nvlist_lookup_string(cfg->config_global_properties, + "alias", &galias); + (void) nvlist_lookup_string(cfg->config_global_properties, + "auth", &gauth); + } + + for (; ptr != NULL; ptr = ptr->tgt_next) { + if (found) { + break; + } + + if (tgt) { + if (strcmp(tgt, ptr->tgt_name) != 0) { + continue; + } else { + found = B_TRUE; + } + } + + state = "-"; + num_sessions = 0; + sess = NULL; + + /* + * make a best effort to retrieve target status and + * number of active sessions from STMF. + */ + ret = stmfDevidFromIscsiName(ptr->tgt_name, &devid); + if (ret == STMF_STATUS_SUCCESS) { + ret = stmfGetTargetProperties(&devid, &props); + if (ret == STMF_STATUS_SUCCESS) { + if (props.status == STMF_TARGET_PORT_ONLINE) { + state = "online"; + } else { + state = "offline"; + } + } + } + if (ret == STMF_STATUS_SUCCESS) { + ret = stmfGetSessionList(&devid, &sess); + if (ret == STMF_STATUS_SUCCESS) { + num_sessions = sess->cnt; + free(sess); + } + } + + /* reset ret so we don't return an error */ + ret = 0; + + if (!script && first) { + (void) printf("%-61s%-9s%-9s\n", "TARGET NAME", + "STATE", "SESSIONS"); + first = B_FALSE; + } + + if (!script) { + /* + * try not to let columns run into each other. + * Stick a tab after too-long fields. + * Lengths chosen are for the 'common' cases. + */ + (void) printf("%-61s", ptr->tgt_name); + if (strlen(ptr->tgt_name) > 60) { + (void) printf("\t"); + } + (void) printf("%-9s%-9d", state, num_sessions); + } else { + (void) printf("%s\t%s\t%d", ptr->tgt_name, + state, num_sessions); + } + + if (!verbose) { + (void) printf("\n"); + continue; + } + + auth = gauth; + alias = galias; + chapu = "-"; + chaps = "unset"; + + if (ptr->tgt_properties) { + (void) nvlist_lookup_string(ptr->tgt_properties, + "auth", &auth); + (void) nvlist_lookup_string(ptr->tgt_properties, + "alias", &alias); + if (nvlist_exists(ptr->tgt_properties, + "targetchapsecret")) { + chaps = "set"; + } + (void) nvlist_lookup_string(ptr->tgt_properties, + "targetchapuser", &chapu); + } + + if (!script) { + (void) printf("\n\t%-20s\t%s\n\t%-20s\t%s\n" + "\t%-20s\t%s\n\t%-20s\t%s\n\t%-20s\t", + "alias:", alias, "auth:", auth, "targetchapuser:", + chapu, "targetchapsecret:", chaps, "tpg-tags:"); + } else { + (void) printf("\t%s\t%s\t%s\t%s\t", + alias, auth, chapu, chaps); + } + + first_tag = B_TRUE; + tagp = ptr->tgt_tpgt_list; + for (; tagp != NULL; tagp = tagp->tpgt_next) { + if (!first_tag) { + (void) printf(","); + } else { + first_tag = B_FALSE; + } + (void) printf("%s", tagp->tpgt_tpg_name); + } + + if (first_tag) { + /* didn't find any */ + (void) printf("default"); + } + + (void) printf("\n"); + } + + if (tgt && (!found)) { + (void) fprintf(stderr, + gettext("Target %s not found!\n"), tgt); + (void) fprintf(stderr, "\n"); + ret = 1; + } + + it_config_free(cfg); + + return (ret); +} + +int +delete_target(char *tgt, boolean_t force) +{ + int ret; + it_config_t *cfg; + it_tgt_t *ptr; + char *sec = "solaris.smf.modify.stmf"; + + ITADM_CHKAUTH(sec); + + if (!tgt) { + (void) fprintf(stderr, "%s\n", + gettext("Error, no target specified")); + return (EINVAL); + } + + ret = it_config_load(&cfg); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error retrieving iSCSI target configuration: %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + ptr = cfg->config_tgt_list; + while (ptr) { + if (strcmp(ptr->tgt_name, tgt) == 0) { + break; + } + + ptr = ptr->tgt_next; + } + + if (ptr) { + ret = it_tgt_delete(cfg, ptr, force); + + if (ret != 0) { + if (ret == EBUSY) { + (void) fprintf(stderr, + gettext("The target is online or busy. " + "Use the -f (force) option, or " + "'stmfadm offline-target %s'"), tgt); + (void) fprintf(stderr, "\n"); + } + } + + if (ret == 0) { + ret = it_config_commit(cfg); + STMF_STALE(ret); + } + } else { + (void) fprintf(stderr, + gettext("Target %s not found"), tgt); + (void) fprintf(stderr, "\n"); + ret = 1; + } + + it_config_free(cfg); + + return (ret); +} + +static int +modify_target(char *tgt, char *newname, nvlist_t *proplist) +{ + int ret; + it_config_t *cfg = NULL; + it_tgt_t *ptr = NULL; + it_tgt_t *tgtp; + char **tags = NULL; + uint32_t count = 0; + nvlist_t *errlist = NULL; + int i; + it_tpg_t *tpg = NULL; + uint16_t tagid; + it_tpgt_t *tpgt; + char *sec = "solaris.smf.modify.stmf"; + + ITADM_CHKAUTH(sec); + + /* XXX: Do we need to offline anything here too? */ + + if (!tgt) { + (void) fprintf(stderr, "%s\n", + gettext("Error, no target specified")); + return (EINVAL); + } + + ret = it_config_load(&cfg); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error retrieving iSCSI target configuration: %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + /* + * If newname is specified, ensure it is a valid name + */ + if (newname) { + if (!validate_iscsi_name(newname)) { + (void) fprintf(stderr, + gettext("Invalid iSCSI name %s"), newname); + (void) fprintf(stderr, "\n"); + return (1); + } + } + + /* + * Loop through to verify that the target to be modified truly + * exists. If this target is to be renamed, ensure the new + * name is not already in use. + */ + ptr = cfg->config_tgt_list; + while (ptr) { + if (newname && (strcmp(newname, ptr->tgt_name) == 0)) { + (void) fprintf(stderr, + gettext("A target with name %s already exists"), + newname); + (void) fprintf(stderr, "\n"); + ret = 1; + goto done; + } + + if (strcmp(ptr->tgt_name, tgt) == 0) { + tgtp = ptr; + } + + ptr = ptr ->tgt_next; + } + + if (!tgtp) { + (void) fprintf(stderr, + gettext("Target %s not found"), tgt); + (void) fprintf(stderr, "\n"); + it_config_free(cfg); + return (EINVAL); + } + + /* set the target portal group tags */ + ret = nvlist_lookup_string_array(proplist, "tpg-tag", &tags, + &count); + + if (ret == ENOENT) { + /* none specified. is this ok? */ + ret = 0; + } else if (ret != 0) { + (void) fprintf(stderr, + gettext("internal error: %d"), ret); + (void) fprintf(stderr, "\n"); + goto done; + } + + /* special case, remove all explicit TPGs, and don't add any */ + if (tags && (strcmp("default", tags[0]) == 0)) { + count = 0; + } + + for (i = 0; i < count; i++) { + if (!tags[i]) { + continue; + } + + /* see that all referenced groups are already defined */ + tpg = cfg->config_tpg_list; + while (tpg != NULL) { + if (strcmp(tags[i], tpg->tpg_name) == 0) { + break; + } + tpg = tpg->tpg_next; + } + if (tpg == NULL) { + (void) fprintf(stderr, + gettext("Invalid tpg-name %s: not defined"), + tags[i]); + (void) fprintf(stderr, "\n"); + ret = 1; + goto done; + } + } + + /* + * don't recreate tags that are already associated, + * remove tags not requested. + */ + if (tags) { + tpgt = tgtp->tgt_tpgt_list; + while (tpgt) { + for (i = 0; i < count; i++) { + if (!tags[i]) { + continue; + } + + if (strcmp(tpgt->tpgt_tpg_name, tags[i]) + == 0) { + /* non-null tags will be created */ + tags[i] = NULL; + break; + } + } + if (i == count) { + /* one to remove */ + it_tpgt_t *ptr = tpgt; + + tpgt = ptr->tpgt_next; + it_tpgt_delete(cfg, tgtp, ptr); + } else { + tpgt = tpgt->tpgt_next; + } + } + } + + /* see if there are any left to add */ + for (i = 0; i < count; i++) { + if (!tags[i]) { + continue; + } + + /* generate the tag number to use */ + tag_name_to_num(tags[i], &tagid); + + ret = it_tpgt_create(cfg, tgtp, &tpgt, tags[i], tagid); + if (ret != 0) { + if (ret == E2BIG) { + (void) fprintf(stderr, "%s\n", + gettext("Error, no portal tag available")); + } else { + (void) fprintf(stderr, gettext( + "Could not add target portal group" + " tag %s, error %d"), tags[i], ret); + (void) fprintf(stderr, "\n"); + } + goto done; + } + } + + /* remove the tags from the proplist before continuing */ + (void) nvlist_remove_all(proplist, "tpg-tag"); + + /* + * Rename this target, if requested. Save the old name in + * the property list, so the kernel knows this is a renamed + * target, and not a new one. + */ + if (newname && (strlen(newname) > 0)) { + ret = nvlist_add_string(proplist, "oldtargetname", + tgtp->tgt_name); + if (ret != 0) { + (void) fprintf(stderr, "%s\n", + gettext("Error renaming target.")); + goto done; + } + (void) strlcpy(tgtp->tgt_name, newname, + sizeof (tgtp->tgt_name)); + } + + ret = it_tgt_setprop(cfg, tgtp, proplist, &errlist); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error setting target properties: %d"), ret); + (void) fprintf(stderr, "\n"); + if (errlist) { + nvpair_t *nvp = NULL; + char *nn; + char *nv; + + while ((nvp = nvlist_next_nvpair(errlist, nvp)) + != NULL) { + nv = NULL; + + nn = nvpair_name(nvp); + (void) nvpair_value_string(nvp, &nv); + + if (nv != NULL) { + (void) fprintf(stderr, "\t%s: %s\n", + nn, nv); + } + } + + nvlist_free(errlist); + } + goto done; + } + + if (ret == 0) { + ret = it_config_commit(cfg); + STMF_STALE(ret); + } + +done: + if (ret == 0) { + (void) printf(gettext("Target %s successfully modified"), + tgtp->tgt_name); + (void) printf("\n"); + } + + it_config_free(cfg); + + return (ret); +} + +int +create_tpg(char *tpg, int addrc, char **addrs) +{ + int ret; + it_config_t *cfg; + it_tpg_t *tpgp; + int count = 0; + it_portal_t *ptl; + char *sec = "solaris.smf.modify.stmf"; + + ITADM_CHKAUTH(sec); + + if (!tpg) { + (void) fprintf(stderr, "%s\n", + gettext("Error, no target portal group specified")); + return (EINVAL); + } + + if (strlen(tpg) > (MAX_TPG_NAMELEN - 1)) { + (void) fprintf(stderr, + gettext("Target Portal Group name must be no longer " + "than %d characters."), (MAX_TPG_NAMELEN - 1)); + (void) fprintf(stderr, "\n"); + return (EINVAL); + } + + if (!addrs || (addrc <= 0)) { + (void) fprintf(stderr, "%s\n", + gettext("Error, no portal addresses specified")); + return (EINVAL); + } + + ret = it_config_load(&cfg); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error retrieving iSCSI target configuration: %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + tpgp = cfg->config_tpg_list; + while (tpgp != NULL) { + if (strcmp(tpgp->tpg_name, tpg) == 0) { + (void) fprintf(stderr, + gettext("Target Portal Group %s already exists"), + tpg); + (void) fprintf(stderr, "\n"); + it_config_free(cfg); + return (1); + } + tpgp = tpgp->tpg_next; + } + + /* + * Create the portal group and first portal + */ + ret = it_tpg_create(cfg, &tpgp, tpg, addrs[count]); + if (ret != 0) { + if (ret == EEXIST) { + (void) fprintf(stderr, + gettext("Portal %s already in use"), + addrs[count]); + (void) fprintf(stderr, "\n"); + } + it_config_free(cfg); + return (ret); + } + + /* + * Add the remaining portals + */ + for (count = 1; count < addrc; count++) { + if (!addrs[count]) { + continue; + } + + ret = it_portal_create(cfg, tpgp, &ptl, addrs[count]); + if (ret != 0) { + if (ret == EEXIST) { + (void) fprintf(stderr, + gettext("Portal %s already in use"), + addrs[count]); + (void) fprintf(stderr, "\n"); + } else { + (void) fprintf(stderr, + gettext("Error adding portal %s: %d"), + addrs[count], ret); + (void) fprintf(stderr, "\n"); + break; + } + } + } + + if (ret == 0) { + ret = it_config_commit(cfg); + STMF_STALE(ret); + } + + it_config_free(cfg); + + return (ret); +} + +static int +list_tpg(char *tpg, boolean_t verbose, boolean_t script) +{ + int ret; + it_config_t *cfg; + it_tpg_t *ptr; + boolean_t found = B_FALSE; + it_portal_t *portal; + boolean_t first = B_TRUE; + boolean_t first_portal; + char *pstr; + char *sec = "solaris.smf.read.stmf"; + + ITADM_CHKAUTH(sec); + + ret = it_config_load(&cfg); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error retrieving iSCSI target configuration: %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + ptr = cfg->config_tpg_list; + + for (; ptr != NULL; ptr = ptr->tpg_next) { + if (found) { + break; + } + + if (tpg) { + if (strcmp(tpg, ptr->tpg_name) != 0) { + continue; + } else { + found = B_TRUE; + } + } + + if (!script && first) { + (void) printf("%-30s%-9s\n", "TARGET PORTAL GROUP", + "PORTAL COUNT"); + first = B_FALSE; + } + + if (!script) { + (void) printf("%-30s", ptr->tpg_name); + if (strlen(ptr->tpg_name) > 30) { + (void) printf("\t"); + } + (void) printf("%-9d", ptr->tpg_portal_count); + } else { + (void) printf("%s\t%d", ptr->tpg_name, + ptr->tpg_portal_count); + } + + if (!verbose) { + (void) printf("\n"); + continue; + } + + if (!script) { + (void) printf("\n portals:"); + } + + first_portal = B_TRUE; + + portal = ptr->tpg_portal_list; + for (; portal != NULL; portal = portal->next) { + ret = sockaddr_to_str(&(portal->portal_addr), &pstr); + if (ret != 0) { + /* invalid addr? */ + continue; + } + if (!first_portal) { + (void) printf(","); + } else { + (void) printf("\t"); + first_portal = B_FALSE; + } + + (void) printf("%s", pstr); + free(pstr); + } + + if (first_portal) { + /* none found */ + (void) printf("\t"); + } + + (void) printf("\n"); + } + + if (tpg && (!found)) { + (void) fprintf(stderr, + gettext("Target Portal Group %s not found!\n"), tpg); + (void) fprintf(stderr, "\n"); + ret = 1; + } + + it_config_free(cfg); + + return (ret); +} + +static int +delete_tpg(char *tpg, boolean_t force) +{ + int ret; + it_config_t *cfg; + it_tpg_t *ptpg = NULL; + char *sec = "solaris.smf.modify.stmf"; + + ITADM_CHKAUTH(sec); + + if (!tpg) { + (void) fprintf(stderr, "%s\n", + gettext("Error, no target portal group specified")); + return (EINVAL); + } + + ret = it_config_load(&cfg); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error retrieving iSCSI target configuration: %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + ptpg = cfg->config_tpg_list; + for (; ptpg != NULL; ptpg = ptpg->tpg_next) { + if (strcmp(tpg, ptpg->tpg_name) == 0) { + break; + } + } + + if (!ptpg) { + (void) fprintf(stderr, + gettext("Target portal group %s does not exist."), + tpg); + (void) fprintf(stderr, "\n"); + ret = 1; + } else { + ret = it_tpg_delete(cfg, ptpg, force); + if (ret == EBUSY) { + (void) fprintf(stderr, "%s\n", + gettext( + "Target portal group associated with one or more " + "targets. Cannot delete.")); + } + + if (ret == 0) { + ret = it_config_commit(cfg); + STMF_STALE(ret); + } + } + + it_config_free(cfg); + + return (ret); +} + +static int +modify_initiator(char *ini, nvlist_t *proplist, boolean_t create) +{ + int ret; + it_config_t *cfg; + it_ini_t *inip; + nvlist_t *errlist = NULL; + nvpair_t *nvp = NULL; + char *sec = "solaris.smf.modify.stmf"; + boolean_t changed = B_TRUE; + + ITADM_CHKAUTH(sec); + + if (!ini) { + (void) fprintf(stderr, "%s\n", + gettext("Error, no initiator specified")); + return (EINVAL); + } else if (create) { + /* + * validate input name - what are the rules for EUI + * and IQN values? + */ + if ((strncmp(ini, "eui.", 4) != 0) && + (strncmp(ini, "iqn.", 4) != 0)) { + (void) fprintf(stderr, gettext("Invalid name %s"), + ini); + (void) fprintf(stderr, "\n"); + return (EINVAL); + } + } + + /* + * See if any properties were actually specified. + */ + if (proplist) { + nvp = nvlist_next_nvpair(proplist, nvp); + } + + if ((nvp == NULL) && !create) { + changed = B_FALSE; + } + + /* + * If no properties, and this is really a modify op, verify + * that the requested initiator exists, but then don't do anything. + * Modifying non-existent is an error; doing nothing to a defined + * initiator is not. + */ + + ret = it_config_load(&cfg); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error retrieving iSCSI target configuration: %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + inip = cfg->config_ini_list; + while (inip) { + if (strcmp(inip->ini_name, ini) == 0) { + break; + } + + inip = inip->ini_next; + } + + if (create) { + if (inip) { + (void) fprintf(stderr, + gettext("Initiator %s already exists"), + inip->ini_name); + (void) fprintf(stderr, "\n"); + ret = EINVAL; + } else { + ret = it_ini_create(cfg, &inip, ini); + if (ret != 0) { + if (ret == EFAULT) { + (void) fprintf(stderr, + gettext("Invalid iSCSI name %s"), + ini); + } else { + (void) fprintf(stderr, + gettext( + "Error creating initiator: %d"), + ret); + } + (void) fprintf(stderr, "\n"); + } + } + } else if (!inip) { + ret = ENOENT; + (void) fprintf(stderr, + gettext("Error, initiator %s not found."), + ini); + (void) fprintf(stderr, "\n"); + } + + if ((ret == 0) && nvp) { + ret = it_ini_setprop(inip, proplist, &errlist); + + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error setting initiator properties: %d"), + ret); + (void) fprintf(stderr, "\n"); + if (errlist) { + nvpair_t *nvp = NULL; + char *nn; + char *nv; + + while ((nvp = nvlist_next_nvpair(errlist, nvp)) + != NULL) { + nv = NULL; + + nn = nvpair_name(nvp); + (void) nvpair_value_string(nvp, &nv); + + if (nv != NULL) { + (void) fprintf(stderr, + "\t%s: %s\n", nn, nv); + } + } + + nvlist_free(errlist); + } + } + } + + if ((ret == 0) && changed) { + ret = it_config_commit(cfg); + STMF_STALE(ret); + } + + it_config_free(cfg); + + return (ret); +} + +static int +list_initiator(char *ini, boolean_t verbose, boolean_t script) /* ARGSUSED */ +{ + int ret; + it_config_t *cfg; + it_ini_t *ptr; + boolean_t found = B_FALSE; + boolean_t first = B_TRUE; + char *isecret; + char *iuser; + char *sec = "solaris.smf.read.stmf"; + + ITADM_CHKAUTH(sec); + + ret = it_config_load(&cfg); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error retrieving iSCSI target configuration: %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + ptr = cfg->config_ini_list; + + for (; ptr != NULL; ptr = ptr->ini_next) { + isecret = "unset"; + iuser = ""; + + if (found) { + break; + } + + if (ini) { + if (strcmp(ini, ptr->ini_name) != 0) { + continue; + } else { + found = B_TRUE; + } + } + + if (ptr->ini_properties) { + if (nvlist_exists(ptr->ini_properties, "chapsecret")) { + isecret = "set"; + } + (void) nvlist_lookup_string(ptr->ini_properties, + "chapuser", &iuser); + + } + + /* there's nothing to print for verbose yet */ + if (!script && first) { + (void) printf("%-61s%-10s%-7s\n", "INITIATOR NAME", + "CHAPUSER", "SECRET"); + first = B_FALSE; + } + + if (!script) { + /* + * try not to let columns run into each other. + * Stick a tab after too-long fields. + * Lengths chosen are for the 'common' cases. + */ + (void) printf("%-61s", ptr->ini_name); + + if (strlen(ptr->ini_name) > 60) { + (void) printf("\t"); + } + + (void) printf("%-15s", iuser); + if (strlen(iuser) >= 15) { + (void) printf("\t"); + } + + (void) printf("%-4s", isecret); + } else { + (void) printf("%s\t%s\t%s", ptr->ini_name, + iuser, isecret); + } + + (void) printf("\n"); + } + + if (ini && (!found)) { + (void) fprintf(stderr, + gettext("Initiator %s not found!"), ini); + (void) fprintf(stderr, "\n"); + ret = 1; + } + + it_config_free(cfg); + + return (ret); +} + +int +delete_initiator(char *ini) +{ + int ret; + it_config_t *cfg; + it_ini_t *ptr; + char *sec = "solaris.smf.modify.stmf"; + + ITADM_CHKAUTH(sec); + + if (!ini) { + (void) fprintf(stderr, "%s\n", + gettext("Error, no initiator specified")); + return (EINVAL); + } + + ret = it_config_load(&cfg); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error retrieving iSCSI target configuration: %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + ptr = cfg->config_ini_list; + while (ptr) { + if (strcmp(ptr->ini_name, ini) == 0) { + break; + } + + ptr = ptr->ini_next; + } + + if (ptr) { + it_ini_delete(cfg, ptr); + + ret = it_config_commit(cfg); + STMF_STALE(ret); + } else { + (void) fprintf(stderr, + gettext("Initiator %s not found"), ini); + (void) fprintf(stderr, "\n"); + ret = 1; + } + + return (ret); +} + +static int +modify_defaults(nvlist_t *proplist) +{ + int ret; + it_config_t *cfg; + nvlist_t *errlist = NULL; + nvpair_t *nvp = NULL; + char *sec = "solaris.smf.modify.stmf"; + + ITADM_CHKAUTH(sec); + + if (proplist) { + /* make sure at least one property is specified */ + nvp = nvlist_next_nvpair(proplist, nvp); + } + + if (nvp == NULL) { + /* empty list */ + (void) fprintf(stderr, "%s\n", + gettext("Error, no properties specified")); + return (EINVAL); + } + + ret = it_config_load(&cfg); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error retrieving iSCSI target configuration: %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + ret = it_config_setprop(cfg, proplist, &errlist); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error setting global properties: %d"), + ret); + (void) fprintf(stderr, "\n"); + if (errlist) { + nvpair_t *nvp = NULL; + char *nn; + char *nv; + + while ((nvp = nvlist_next_nvpair(errlist, nvp)) + != NULL) { + nv = NULL; + + nn = nvpair_name(nvp); + (void) nvpair_value_string(nvp, &nv); + + if (nv != NULL) { + (void) fprintf(stderr, "\t%s: %s\n", + nn, nv); + } + } + + nvlist_free(errlist); + } + } + + if (ret == 0) { + ret = it_config_commit(cfg); + STMF_STALE(ret); + } + + it_config_free(cfg); + + return (ret); +} + +static int +list_defaults(boolean_t script) +{ + int ret; + it_config_t *cfg; + nvlist_t *nvl; + char *alias = ""; + char *auth = ""; + char *isns = "disabled"; + char **isvrs = NULL; + uint32_t scount = 0; + char *rsvr = ""; + char *rsecret = "unset"; + boolean_t val = B_FALSE; + int i; + char *sec = "solaris.smf.read.stmf"; + + ITADM_CHKAUTH(sec); + + ret = it_config_load(&cfg); + if (ret != 0) { + (void) fprintf(stderr, + gettext("Error retrieving iSCSI target configuration: %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + nvl = cfg->config_global_properties; + + /* look up all possible options */ + (void) nvlist_lookup_string(nvl, "alias", &alias); + (void) nvlist_lookup_string(nvl, "auth", &auth); + (void) nvlist_lookup_boolean_value(nvl, "isns", &val); + if (val == B_TRUE) { + isns = "enabled"; + } + (void) nvlist_lookup_string_array(nvl, "isnsserver", &isvrs, + &scount); + (void) nvlist_lookup_string(nvl, "radiusserver", &rsvr); + if (nvlist_exists(nvl, "radiussecret")) { + rsecret = "set"; + } + + if (!script) { + (void) printf("%s:\n\n", + gettext("iSCSI Target Default Properties")); + } + + if (script) { + (void) printf("%s\t%s\t%s\t%s\t%s\t", + alias, auth, rsvr, rsecret, isns); + } else { + (void) printf("%-15s\t%s\n%-15s\t%s\n%-15s\t%s\n%-15s\t%s\n" + "%-15s\t%s\n%-15s\t", + "alias:", alias, "auth:", auth, "radiusserver:", + rsvr, "radiussecret:", rsecret, "isns:", isns, + "isnsserver:"); + } + + for (i = 0; i < scount; i++) { + if (!isvrs || !isvrs[i]) { + break; + } + if (i > 0) { + (void) printf(","); + } + (void) printf("%s", isvrs[i]); + } + + if (i == 0) { + (void) printf("%s", ""); + } + + (void) printf("\n"); + + it_config_free(cfg); + + return (0); +} + +static int +itadm_get_password(nvlist_t *nvl, char *key, char *passfile, + char *phrase) +{ + int ret = 0; + char *pass; + char buf[1024]; + int fd; + struct stat64 sbuf; + size_t rd; + + if (!nvl || !key) { + return (EINVAL); + } + + if (passfile) { + ret = stat64(passfile, &sbuf); + if ((ret != 0) || (!S_ISREG(sbuf.st_mode))) { + (void) fprintf(stderr, + gettext("Invalid secret file %s"), + passfile); + (void) fprintf(stderr, "\n"); + return (EBADF); + } + + fd = open64(passfile, O_RDONLY); + if (fd == -1) { + ret = errno; + (void) fprintf(stderr, + gettext("Could not open secret file %s, %d"), + passfile, ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + rd = read(fd, buf, sbuf.st_size); + (void) close(fd); + + if (rd != sbuf.st_size) { + ret = EIO; + (void) fprintf(stderr, + gettext("Could not read secret file %s, %d"), + passfile, ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + /* ensure buf is properly terminated */ + buf[rd] = '\0'; + + /* if last char is a newline, strip it off */ + if (buf[rd - 1] == '\n') { + buf[rd - 1] = '\0'; + } + + /* validate length */ + if ((strlen(buf) > 255) || (strlen(buf) < 12)) { + (void) fprintf(stderr, "%s\n", + gettext( + "Secret must be between 12 and 255 characters")); + return (EINVAL); + } + } else { + /* prompt for secret */ + if (!phrase) { + return (EINVAL); + } + + pass = getpassphrase(phrase); + if (!pass) { + ret = errno; + (void) fprintf(stderr, + gettext("Could not read secret, %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + /* validate length */ + if ((strlen(pass) > 255) || (strlen(pass) < 12)) { + (void) fprintf(stderr, "%s\n", + gettext( + "Secret must be between 12 and 255 characters")); + return (EINVAL); + } + + (void) strlcpy(buf, pass, sizeof (buf)); + + /* confirm entered secret */ + pass = getpassphrase(gettext("Re-enter secret: ")); + if (!pass) { + ret = errno; + (void) fprintf(stderr, + gettext("Could not read secret, %d"), + ret); + (void) fprintf(stderr, "\n"); + return (ret); + } + + if (strcmp(buf, pass) != 0) { + ret = EINVAL; + (void) fprintf(stderr, "%s\n", + gettext("Secret validation failed")); + return (ret); + } + + } + + ret = nvlist_add_string(nvl, key, buf); + + return (ret); +} + +static int +itadm_opt_to_arr(nvlist_t *nvl, char *key, char *opt, uint32_t *num) +{ + int count; + char *bufp; + char **arr; + + if (!opt || !key || !nvl) { + return (EINVAL); + } + + bufp = opt; + count = 1; + + for (;;) { + bufp = strchr(bufp, ','); + if (!bufp) { + break; + } + bufp++; + count++; + } + + arr = calloc(count, sizeof (char *)); + if (!arr) { + return (ENOMEM); + } + + bufp = opt; + /* set delimiter to comma */ + (void) bufsplit(",", 0, NULL); + + /* split up that buf! */ + (void) bufsplit(bufp, count, arr); + + /* if requested, return the number of array members found */ + if (num) { + *num = count; + } + + return (nvlist_add_string_array(nvl, key, arr, count)); +} + +static void +tag_name_to_num(char *tagname, uint16_t *tagnum) +{ + ulong_t id; + char *ptr = NULL; + + if (!tagname || !tagnum) { + return; + } + + *tagnum = 0; + + id = strtoul(tagname, &ptr, 10); + + /* Must be entirely numeric and in-range */ + if (ptr && (*ptr != '\0')) { + return; + } + + if ((id <= UINT16_MAX) && (id > 1)) { + *tagnum = (uint16_t)id; + } +} diff --git a/usr/src/cmd/mdb/Makefile.common b/usr/src/cmd/mdb/Makefile.common index 855679b9955a..5677289bc9c5 100644 --- a/usr/src/cmd/mdb/Makefile.common +++ b/usr/src/cmd/mdb/Makefile.common @@ -62,6 +62,7 @@ COMMON_MODULES_KVM = \ genunix \ hook \ neti \ + idm \ ii \ ip \ ipc \ diff --git a/usr/src/cmd/mdb/common/modules/idm/idm.c b/usr/src/cmd/mdb/common/modules/idm/idm.c new file mode 100644 index 000000000000..210e90a84f45 --- /dev/null +++ b/usr/src/cmd/mdb/common/modules/idm/idm.c @@ -0,0 +1,2480 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include /* networking stuff */ +#include /* networking stuff */ +#include +#include +#include +#include +#include + +#define IDM_CONN_SM_STRINGS +#define ISCSIT_TGT_SM_STRINGS +#define ISCSIT_SESS_SM_STRINGS +#define ISCSIT_LOGIN_SM_STRINGS +#include +#include +#include + + +/* + * We want to be able to print multiple levels of object hierarchy with a + * single dcmd information, and preferably also exclude intermediate + * levels if desired. For example some of the target objects have the + * following relationship: + * + * target --> session --> connection --> task + * + * The session dcmd should allow the printing of all associated tasks for the + * sessions without printing all the associated connections. To accomplish + * this the following structure contains a bit for each object type. Dcmds + * should invoked the functions for child objects if any bits are set + * in iscsi_dcmd_ctrl_t but the functions for the child object should only + * print data if their associated bit is set. + * + * Each dcmd should provide an external interface with the standard MDB API + * and an internal interface that accepts iscsi_dcmd_ctrl_t. To display + * child objects the dcmd calls the internal interface for the child object + * directly. Dcmds invoked from the command line will, of course, call the + * external interface. See iscsi_conn() and iscsi_conn_impl(). + */ + +typedef struct { + union { + uint32_t idc_children; + struct { + uint32_t idc_tgt:1, + idc_tpgt:1, + idc_portal:1, + idc_sess:1, + idc_conn:1, + idc_print_ip:1, + idc_task:1, + idc_buffer:1, + idc_states:1, + idc_rc_audit:1, + idc_lun:1, + idc_hba:1; + } child; + } u; + boolean_t idc_ini; + boolean_t idc_tgt; + boolean_t idc_verbose; + boolean_t idc_header; + /* + * Our connection dcmd code works off the global connection lists + * in IDM since we want to know about connections even when they + * have not progressed to the point that they have an associated + * session. If we use "::iscsi_sess [-c]" then we only want to + * see connections associated with particular session. To avoid + * writing a separate set of code to print session-specific connection + * the session code should set the sessions kernel address in the + * following field. The connection code will then only print + * connections that match. + */ + uintptr_t idc_assoc_session; +} iscsi_dcmd_ctrl_t; + +static int iscsi_walk_all_sess(iscsi_dcmd_ctrl_t *idc); +static int iscsi_walk_all_conn(iscsi_dcmd_ctrl_t *idc); +static int iscsi_tgt_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void); +static int iscsi_tpgt_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void); +static int iscsi_tpg_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void); +static int iscsi_portal_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void); +static int iscsi_sess_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void); +static int iscsi_conn_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void); +static int iscsi_buffer_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void); +static int iscsi_tgt_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); +static int iscsi_tpgt_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); +static int iscsi_tpg_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); +static int iscsi_portal_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); +static int iscsi_sess_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); +static int iscsi_conn_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); +static void iscsi_print_iscsit_conn_data(idm_conn_t *ict); +static void iscsi_print_idm_conn_data(idm_conn_t *ict); +static int iscsi_task_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); +static void iscsi_print_iscsit_task_data(idm_task_t *idt); +static int iscsi_buffer_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc); +static idm_conn_type_t idm_conn_type(uintptr_t addr); +static int iscsi_i_task_impl(idm_task_t *idt, uintptr_t addr, + iscsi_dcmd_ctrl_t *idc); +static int iscsi_refcnt_impl(uintptr_t addr); +static int iscsi_sm_audit_impl(uintptr_t addr); +static int iscsi_isns(uintptr_t addr, uint_t flags, int argc, + const mdb_arg_t *argv); + +static const char *iscsi_idm_conn_event(int event); +static const char *iscsi_iscsit_tgt_event(int event); +static const char *iscsi_iscsit_sess_event(int event); +static const char *iscsi_iscsit_login_event(int event); +static const char *iscsi_idm_conn_state(int state); +static const char *iscsi_idm_task_state(int state); +static const char *iscsi_iscsit_tgt_state(int state); +static const char *iscsi_iscsit_sess_state(int state); +static const char *iscsi_iscsit_login_state(int state); + +static void iscsi_format_timestamp(char *ts_str, int strlen, + timespec_t *ts); +static char *inet_ntop(int af, const void *addr, char *buf, int addrlen); +static void convert2ascii(char *, const in6_addr_t *); +static int sa_to_str(struct sockaddr_storage *sa, char *addr); +static int iscsi_isns_portal_cb(uintptr_t addr, const void *walker_data, + void *data); + +#define PORTAL_STR_LEN (INET6_ADDRSTRLEN + 7) + +/* + * ::iscsi_tgt [-scatgpbSRv] + * + * iscsi_tgt - Print out information associated with an iscsit target instance + * + * s Print associated session information + * c Print associated connection information + * a Print IP addresses with connection information + * t Print associated task information + * g Print associated TPG information + * p Print portals with TPG information + * b Print associated buffer information + * S Print recent state events and transitions + * R Print reference count audit data + * v Verbose output about the connection + */ +/*ARGSUSED*/ +static int +iscsi_tgt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + iscsi_dcmd_ctrl_t idc; + int buffer = 0, task = 0, print_ip = 0; + int tpgt = 0, conn = 0, sess = 0, portal = 0; + int states = 0, rc_audit = 0; + uintptr_t iscsit_global_addr, avl_addr, list_addr; + GElf_Sym sym; + + bzero(&idc, sizeof (idc)); + if (mdb_getopts(argc, argv, + 'a', MDB_OPT_SETBITS, TRUE, &print_ip, + 'g', MDB_OPT_SETBITS, TRUE, &tpgt, + 's', MDB_OPT_SETBITS, TRUE, &sess, + 'c', MDB_OPT_SETBITS, TRUE, &conn, + 't', MDB_OPT_SETBITS, TRUE, &task, + 'b', MDB_OPT_SETBITS, TRUE, &buffer, + 'p', MDB_OPT_SETBITS, TRUE, &portal, + 'S', MDB_OPT_SETBITS, TRUE, &states, + 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, + 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose, + NULL) != argc) + return (DCMD_USAGE); + + idc.u.child.idc_tgt = 1; + idc.u.child.idc_print_ip = print_ip; + idc.u.child.idc_tpgt = tpgt; + idc.u.child.idc_portal = portal; + idc.u.child.idc_sess = sess; + idc.u.child.idc_conn = conn; + idc.u.child.idc_task = task; + idc.u.child.idc_buffer = buffer; + idc.u.child.idc_states = states; + idc.u.child.idc_rc_audit = rc_audit; + + if (DCMD_HDRSPEC(flags)) + idc.idc_header = 1; + + /* + * If no address was specified on the command line, we + * print out all tgtions + */ + if (!(flags & DCMD_ADDRSPEC)) { + if (mdb_lookup_by_name("iscsit_global", &sym) == -1) { + mdb_warn("failed to find symbol 'iscsit_global'"); + return (DCMD_ERR); + } + iscsit_global_addr = (uintptr_t)sym.st_value; + avl_addr = iscsit_global_addr + + offsetof(iscsit_global_t, global_target_list); + if (mdb_pwalk("avl", iscsi_tgt_walk_cb, &idc, avl_addr) == -1) { + mdb_warn("avl walk failed for global target tree"); + return (DCMD_ERR); + } + list_addr = iscsit_global_addr + + offsetof(iscsit_global_t, global_deleted_target_list); + if (mdb_pwalk("list", iscsi_tgt_walk_cb, + &idc, list_addr) == -1) { + mdb_warn("list walk failed for deleted target list"); + return (DCMD_ERR); + } + return (DCMD_OK); + } else { + return (iscsi_tgt_impl(addr, &idc)); + } + /*NOTREACHED*/ +} + +static int +iscsi_tpg(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + iscsi_dcmd_ctrl_t idc; + uintptr_t iscsit_global_addr, avl_addr; + GElf_Sym sym; + + bzero(&idc, sizeof (idc)); + if (mdb_getopts(argc, argv, + 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose, + NULL) != argc) + return (DCMD_USAGE); + + idc.u.child.idc_portal = 1; /* Always print portals */ + if (DCMD_HDRSPEC(flags)) + idc.idc_header = 1; + + /* + * If no address was specified on the command line, we + * print out all tgtions + */ + if (!(flags & DCMD_ADDRSPEC)) { + if (mdb_lookup_by_name("iscsit_global", &sym) == -1) { + mdb_warn("failed to find symbol 'iscsit_global'"); + return (DCMD_ERR); + } + iscsit_global_addr = (uintptr_t)sym.st_value; + avl_addr = iscsit_global_addr + + offsetof(iscsit_global_t, global_tpg_list); + if (mdb_pwalk("avl", iscsi_tpg_walk_cb, &idc, avl_addr) == -1) { + mdb_warn("avl walk failed for global target tree"); + return (DCMD_ERR); + } + return (DCMD_OK); + } else { + return (iscsi_tpg_impl(addr, &idc)); + } + /*NOTREACHED*/ +} + +/* + * ::iscsi_sess [-bctvIT] + * + * iscsi_sess - Print out information associated with an iSCSI session + * + * I Print only initiator sessions + * T Print only target sessions + * c Print associated connection information + * a Print IP addresses with connection information + * t Print associated task information + * b Print associated buffer information + * S Print recent state events and transitions + * R Print reference count audit data + * v Verbose output about the connection + */ +/*ARGSUSED*/ +static int +iscsi_sess(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + iscsi_dcmd_ctrl_t idc; + int buffer = 0, task = 0, conn = 0, print_ip = 0; + int states = 0, rc_audit = 0; + + bzero(&idc, sizeof (idc)); + if (mdb_getopts(argc, argv, + 'I', MDB_OPT_SETBITS, TRUE, &idc.idc_ini, + 'T', MDB_OPT_SETBITS, TRUE, &idc.idc_tgt, + 'a', MDB_OPT_SETBITS, TRUE, &print_ip, + 'c', MDB_OPT_SETBITS, TRUE, &conn, + 't', MDB_OPT_SETBITS, TRUE, &task, + 'b', MDB_OPT_SETBITS, TRUE, &buffer, + 'S', MDB_OPT_SETBITS, TRUE, &states, + 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, + 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose, + NULL) != argc) + return (DCMD_USAGE); + + idc.u.child.idc_sess = 1; + idc.u.child.idc_print_ip = print_ip; + idc.u.child.idc_conn = conn; + idc.u.child.idc_task = task; + idc.u.child.idc_buffer = buffer; + idc.u.child.idc_states = states; + idc.u.child.idc_rc_audit = rc_audit; + if (DCMD_HDRSPEC(flags)) + idc.idc_header = 1; + + /* + * If no address was specified on the command line, we + * print out all sessions + */ + if (!(flags & DCMD_ADDRSPEC)) { + return (iscsi_walk_all_sess(&idc)); + } else { + return (iscsi_sess_impl(addr, &idc)); + } + /*NOTREACHED*/ +} + + + +/* + * ::iscsi_conn [-btvIT] + * + * iscsi_conn - Print out information associated with an iSCSI connection + * + * I Print only initiator connections + * T Print only target connections + * a Print IP addresses with connection information + * t Print associated task information + * b Print associated buffer information + * S Print recent state events and transitions + * R Print reference count audit data + * v Verbose output about the connection + */ +/*ARGSUSED*/ +static int +iscsi_conn(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + iscsi_dcmd_ctrl_t idc; + int buffer = 0, task = 0, print_ip = 0; + int states = 0, rc_audit = 0; + + bzero(&idc, sizeof (idc)); + if (mdb_getopts(argc, argv, + 'I', MDB_OPT_SETBITS, TRUE, &idc.idc_ini, + 'T', MDB_OPT_SETBITS, TRUE, &idc.idc_tgt, + 'a', MDB_OPT_SETBITS, TRUE, &print_ip, + 't', MDB_OPT_SETBITS, TRUE, &task, + 'b', MDB_OPT_SETBITS, TRUE, &buffer, + 'S', MDB_OPT_SETBITS, TRUE, &states, + 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, + 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose, + NULL) != argc) + return (DCMD_USAGE); + + idc.u.child.idc_conn = 1; + idc.u.child.idc_print_ip = print_ip; + idc.u.child.idc_task = task; + idc.u.child.idc_buffer = buffer; + idc.u.child.idc_states = states; + idc.u.child.idc_rc_audit = rc_audit; + if (DCMD_HDRSPEC(flags)) + idc.idc_header = 1; + + /* + * If no address was specified on the command line, we + * print out all connections + */ + if (!(flags & DCMD_ADDRSPEC)) { + return (iscsi_walk_all_conn(&idc)); + } else { + return (iscsi_conn_impl(addr, &idc)); + } + /*NOTREACHED*/ +} + +/* + * ::iscsi_task [-bv] + * + * iscsi_task - Print out information associated with an iSCSI task + * + * b Print associated buffer information + * S Print recent state events and transitions + * R Print reference count audit data + * v Verbose output about the connection + */ +/*ARGSUSED*/ +static int +iscsi_task(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + iscsi_dcmd_ctrl_t idc; + int buffer = 0; + int states = 0, rc_audit = 0; + + bzero(&idc, sizeof (idc)); + if (mdb_getopts(argc, argv, + 'b', MDB_OPT_SETBITS, TRUE, &buffer, + 'S', MDB_OPT_SETBITS, TRUE, &states, + 'R', MDB_OPT_SETBITS, TRUE, &rc_audit, + 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose, + NULL) != argc) + return (DCMD_USAGE); + + idc.u.child.idc_conn = 0; + idc.u.child.idc_task = 1; + idc.u.child.idc_buffer = buffer; + idc.u.child.idc_states = states; + idc.u.child.idc_rc_audit = rc_audit; + if (DCMD_HDRSPEC(flags)) + idc.idc_header = 1; + + /* + * If no address was specified on the command line, we + * print out all connections + */ + if (!(flags & DCMD_ADDRSPEC)) { + return (iscsi_walk_all_conn(&idc)); + } else { + return (iscsi_task_impl(addr, &idc)); + } + /*NOTREACHED*/ +} + +/* + * ::iscsi_refcnt + * + * iscsi_refcnt - Dump an idm_refcnt_t structure + * + */ +/*ARGSUSED*/ +static int +iscsi_refcnt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + if (!(flags & DCMD_ADDRSPEC)) { + return (DCMD_ERR); + } else { + return (iscsi_refcnt_impl(addr)); + } + /*NOTREACHED*/ +} + +/* + * ::iscsi_states + * + * iscsi_states - Dump events and state transitions recoreded in an + * idm_sm_audit_t structure + * + */ +/*ARGSUSED*/ +static int +iscsi_states(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + if (!(flags & DCMD_ADDRSPEC)) { + return (DCMD_ERR); + } else { + return (iscsi_sm_audit_impl(addr)); + } + /*NOTREACHED*/ +} + +static int +iscsi_walk_all_sess(iscsi_dcmd_ctrl_t *idc) +{ + uintptr_t iscsit_global_addr; + uintptr_t avl_addr; + uintptr_t list_addr; + GElf_Sym sym; + + /* Walk discovery sessions */ + if (mdb_lookup_by_name("iscsit_global", &sym) == -1) { + mdb_warn("failed to find symbol 'iscsit_global'"); + return (DCMD_ERR); + } + iscsit_global_addr = (uintptr_t)sym.st_value; + avl_addr = iscsit_global_addr + + offsetof(iscsit_global_t, global_discovery_sessions); + if (mdb_pwalk("avl", iscsi_sess_walk_cb, idc, avl_addr) == -1) { + mdb_warn("avl walk failed for discovery sessions"); + return (DCMD_ERR); + } + + /* Walk targets printing all session info */ + avl_addr = iscsit_global_addr + + offsetof(iscsit_global_t, global_target_list); + if (mdb_pwalk("avl", iscsi_tgt_walk_cb, idc, avl_addr) == -1) { + mdb_warn("avl walk failed for target/session tree"); + return (DCMD_ERR); + } + + /* Walk deleting targets printing all session info */ + list_addr = iscsit_global_addr + + offsetof(iscsit_global_t, global_deleted_target_list); + if (mdb_pwalk("list", iscsi_tgt_walk_cb, idc, list_addr) == -1) { + mdb_warn("list walk failed for deleted target list"); + return (DCMD_ERR); + } + + return (DCMD_OK); +} + +static int +iscsi_walk_all_conn(iscsi_dcmd_ctrl_t *idc) +{ + uintptr_t idm_global_addr; + uintptr_t list_addr; + GElf_Sym sym; + + /* Walk initiator connections */ + if (mdb_lookup_by_name("idm", &sym) == -1) { + mdb_warn("failed to find symbol 'idm'"); + return (DCMD_ERR); + } + idm_global_addr = (uintptr_t)sym.st_value; + /* Walk connection list associated with the initiator */ + list_addr = idm_global_addr + offsetof(idm_global_t, idm_ini_conn_list); + if (mdb_pwalk("list", iscsi_conn_walk_cb, idc, list_addr) == -1) { + mdb_warn("list walk failed for initiator connections"); + return (DCMD_ERR); + } + + /* Walk connection list associated with the target */ + list_addr = idm_global_addr + offsetof(idm_global_t, idm_tgt_conn_list); + if (mdb_pwalk("list", iscsi_conn_walk_cb, idc, list_addr) == -1) { + mdb_warn("list walk failed for target service instances"); + return (DCMD_ERR); + } + + return (DCMD_OK); +} + +/*ARGSUSED*/ +static int +iscsi_tpg_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void) +{ + /* We don't particularly care about the list walker data */ + iscsi_dcmd_ctrl_t *idc = idc_void; + int rc; + + rc = iscsi_tpg_impl(addr, idc); + + return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); +} + +/*ARGSUSED*/ +static int +iscsi_tgt_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void) +{ + /* We don't particularly care about the list walker data */ + iscsi_dcmd_ctrl_t *idc = idc_void; + int rc; + + rc = iscsi_tgt_impl(addr, idc); + + return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); +} + +/*ARGSUSED*/ +static int +iscsi_tpgt_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void) +{ + /* We don't particularly care about the list walker data */ + iscsi_dcmd_ctrl_t *idc = idc_void; + int rc; + + rc = iscsi_tpgt_impl(addr, idc); + + return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); +} + +/*ARGSUSED*/ +static int +iscsi_portal_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void) +{ + /* We don't particularly care about the list walker data */ + iscsi_dcmd_ctrl_t *idc = idc_void; + int rc; + + rc = iscsi_portal_impl(addr, idc); + + return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); +} + +/*ARGSUSED*/ +static int +iscsi_sess_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void) +{ + /* We don't particularly care about the list walker data */ + iscsi_dcmd_ctrl_t *idc = idc_void; + int rc; + + rc = iscsi_sess_impl(addr, idc); + + return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); +} + +/*ARGSUSED*/ +static int +iscsi_sess_conn_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void) +{ + /* We don't particularly care about the list walker data */ + iscsi_dcmd_ctrl_t *idc = idc_void; + iscsit_conn_t ict; + int rc; + + /* + * This function is different from iscsi_conn_walk_cb because + * we get an iscsit_conn_t instead of an idm_conn_t + * + * Read iscsit_conn_t, use to get idm_conn_t pointer + */ + if (mdb_vread(&ict, sizeof (iscsit_conn_t), addr) != + sizeof (iscsit_conn_t)) { + return (DCMD_ERR); + } + rc = iscsi_conn_impl((uintptr_t)ict.ict_ic, idc); + + return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); +} + +/*ARGSUSED*/ +static int +iscsi_conn_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void) +{ + /* We don't particularly care about the list walker data */ + iscsi_dcmd_ctrl_t *idc = idc_void; + int rc; + + rc = iscsi_conn_impl(addr, idc); + + return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); +} + +/*ARGSUSED*/ +static int +iscsi_buffer_walk_cb(uintptr_t addr, const void *list_walker_data, + void *idc_void) +{ + /* We don't particularly care about the list walker data */ + iscsi_dcmd_ctrl_t *idc = idc_void; + int rc; + + rc = iscsi_buffer_impl(addr, idc); + + return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR); +} + +static int +iscsi_tgt_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) +{ + iscsit_tgt_t tgt; + uintptr_t avl_addr, rc_addr, states_addr; + char tgt_name[MAX_ISCSI_NODENAMELEN]; + int verbose, states, rc_audit; + + /* + * Read iscsit_tgt_t + */ + if (mdb_vread(&tgt, sizeof (iscsit_tgt_t), addr) != + sizeof (iscsit_tgt_t)) { + return (DCMD_ERR); + } + + /* + * Read target name if available + */ + if ((tgt.target_name == NULL) || + (mdb_readstr(tgt_name, sizeof (tgt_name), + (uintptr_t)tgt.target_name) == -1)) { + strcpy(tgt_name, "N/A"); + } + + /* + * Brief output + * + * iscsit_tgt_t pointer + * iscsit_tgt_t.target_stmf_state + * iscsit_tgt_t.target_sess_list.avl_numnodes (session count) + * iscsit_tgt_t.target_name; + */ + + verbose = idc->idc_verbose; + states = idc->u.child.idc_states; + rc_audit = idc->u.child.idc_rc_audit; + + /* For now we will ignore the verbose flag */ + if (idc->u.child.idc_tgt) { + /* Print target data */ + if (idc->idc_header) { + mdb_printf("%%-19s %-4s %-8s%\n", + "iscsit_tgt_t", "Sess", "State"); + } + mdb_printf("%-19p %-4d %-8d\n", addr, + tgt.target_sess_list.avl_numnodes, + tgt.target_state); + mdb_printf(" %s\n", tgt_name); + } + + idc->idc_header = 0; + idc->idc_verbose = 0; + + /* + * Print states if requested + */ + if (idc->u.child.idc_tgt && states) { + states_addr = addr + offsetof(iscsit_tgt_t, target_state_audit); + + (void) mdb_inc_indent(4); + mdb_printf("State History:\n"); + if (iscsi_sm_audit_impl(states_addr) != DCMD_OK) + return (DCMD_ERR); + idc->u.child.idc_states = 0; + (void) mdb_dec_indent(4); + } + + /* + * Print refcnt audit data if requested + */ + if (idc->u.child.idc_tgt && rc_audit) { + (void) mdb_inc_indent(4); + mdb_printf("target_sess_refcnt:\n"); + rc_addr = addr + + offsetof(iscsit_tgt_t, target_sess_refcnt); + if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) + return (DCMD_ERR); + + mdb_printf("target_refcnt:\n"); + rc_addr = addr + + offsetof(iscsit_tgt_t, target_refcnt); + + if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) + return (DCMD_ERR); + idc->u.child.idc_rc_audit = 0; + (void) mdb_dec_indent(4); + } + + /* Any child objects to walk? */ + if (idc->u.child.idc_tpgt || idc->u.child.idc_sess || + idc->u.child.idc_conn || idc->u.child.idc_task || + idc->u.child.idc_buffer) { + /* Walk TPGT tree */ + idc->idc_header = 1; + (void) mdb_inc_indent(4); + avl_addr = addr + + offsetof(iscsit_tgt_t, target_tpgt_list); + if (mdb_pwalk("avl", iscsi_tpgt_walk_cb, idc, + avl_addr) == -1) { + mdb_warn("target tpgt list walk failed"); + (void) mdb_dec_indent(4); + return (DCMD_ERR); + } + (void) mdb_dec_indent(4); + + /* Walk sess tree */ + idc->idc_header = 1; + (void) mdb_inc_indent(4); + avl_addr = addr + offsetof(iscsit_tgt_t, target_sess_list); + if (mdb_pwalk("avl", iscsi_sess_walk_cb, idc, + avl_addr) == -1) { + mdb_warn("target sess list walk failed"); + (void) mdb_dec_indent(4); + return (DCMD_ERR); + } + (void) mdb_dec_indent(4); + + idc->idc_header = 0; + } + + idc->idc_verbose = verbose; + idc->u.child.idc_states = states; + idc->u.child.idc_rc_audit = rc_audit; + return (DCMD_OK); +} + +static int +iscsi_tpgt_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) +{ + iscsit_tpgt_t tpgt; + iscsit_tpg_t tpg; + uintptr_t avl_addr, tpg_addr; + + /* + * Read iscsit_tpgt_t + */ + if (mdb_vread(&tpgt, sizeof (iscsit_tpgt_t), addr) != + sizeof (iscsit_tpgt_t)) { + return (DCMD_ERR); + } + + tpg_addr = (uintptr_t)tpgt.tpgt_tpg; + + /* + * Read iscsit_tpg_t + */ + if (mdb_vread(&tpg, sizeof (iscsit_tpg_t), tpg_addr) != + sizeof (iscsit_tpg_t)) { + return (DCMD_ERR); + } + + /* + * Brief output + * + * iscsit_tpgt_t pointer + * iscsit_tpg_t pointer + * iscsit_tpg_t.tpg_name + * iscsit_tpgt_t.tpgt_tag; + */ + + /* For now we will ignore the verbose flag */ + if (idc->u.child.idc_tpgt) { + /* Print target data */ + if (idc->idc_header) { + mdb_printf("%%-?s %-?s %-18s %-6s%\n", + "iscsit_tpgt_t", "iscsit_tpg_t", "Name", "Tag"); + } + mdb_printf("%?p %?p %-18s 0x%04x\n", addr, tpgt.tpgt_tpg, + tpg.tpg_name, tpgt.tpgt_tag); + } + + /* + * Assume for now that anyone interested in TPGT wants to see the + * portals as well. + */ + idc->idc_header = 1; + (void) mdb_inc_indent(4); + avl_addr = tpg_addr + offsetof(iscsit_tpg_t, tpg_portal_list); + if (mdb_pwalk("avl", iscsi_portal_walk_cb, idc, avl_addr) == -1) { + mdb_warn("portal list walk failed"); + (void) mdb_dec_indent(4); + return (DCMD_ERR); + } + (void) mdb_dec_indent(4); + idc->idc_header = 0; + + return (DCMD_OK); +} + +static int +iscsi_tpg_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) +{ + iscsit_tpg_t tpg; + uintptr_t avl_addr; + + /* + * Read iscsit_tpg_t + */ + if (mdb_vread(&tpg, sizeof (iscsit_tpg_t), addr) != + sizeof (iscsit_tpg_t)) { + return (DCMD_ERR); + } + + /* + * Brief output + * + * iscsit_tpgt_t pointer + * iscsit_tpg_t pointer + * iscsit_tpg_t.tpg_name + * iscsit_tpgt_t.tpgt_tag; + */ + + /* For now we will ignore the verbose flag */ + + /* Print target data */ + if (idc->idc_header) { + mdb_printf("%%-?s %-18s%\n", + "iscsit_tpg_t", "Name"); + } + mdb_printf("%?p %-18s\n", addr, tpg.tpg_name); + + + /* + * Assume for now that anyone interested in TPG wants to see the + * portals as well. + */ + idc->idc_header = 1; + (void) mdb_inc_indent(4); + avl_addr = addr + offsetof(iscsit_tpg_t, tpg_portal_list); + if (mdb_pwalk("avl", iscsi_portal_walk_cb, idc, avl_addr) == -1) { + mdb_warn("portal list walk failed"); + (void) mdb_dec_indent(4); + return (DCMD_ERR); + } + (void) mdb_dec_indent(4); + idc->idc_header = 0; + + return (DCMD_OK); +} + +static int +iscsi_portal_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) +{ + iscsit_portal_t portal; + char portal_addr[PORTAL_STR_LEN]; + if (idc->u.child.idc_portal) { + /* + * Read iscsit_portal_t + */ + if (mdb_vread(&portal, sizeof (iscsit_portal_t), addr) != + sizeof (iscsit_portal_t)) { + return (DCMD_ERR); + } + + /* Print portal data */ + if (idc->idc_header) { + mdb_printf("%%-?s %-?s %-30s%\n", + "iscsit_portal_t", "idm_svc_t", "IP:Port"); + } + sa_to_str(&portal.portal_addr, portal_addr); + mdb_printf("%?p %?p %s\n", addr, portal.portal_svc, + portal_addr); + } + + return (DCMD_OK); +} + +static int +iscsi_sess_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) +{ + iscsit_sess_t ist; + uintptr_t list_addr, states_addr, rc_addr; + char ini_name[80]; + char tgt_name[80]; + int verbose, states, rc_audit; + + /* + * Read iscsit_sess_t + */ + if (mdb_vread(&ist, sizeof (iscsit_sess_t), addr) != + sizeof (iscsit_sess_t)) { + return (DCMD_ERR); + } + + /* + * Brief output + * + * iscsit_sess_t pointer + * iscsit_sess_t.ist_state/iscsit_sess_t.ist_ffp_conn_count + * iscsit_sess_t.ist_tsih + * iscsit_sess_t.ist_initiator_name + */ + + verbose = idc->idc_verbose; + states = idc->u.child.idc_states; + rc_audit = idc->u.child.idc_rc_audit; + + if (idc->u.child.idc_sess) { + if (verbose) { + /* + * Read initiator name if available + */ + if ((ist.ist_initiator_name == NULL) || + (mdb_readstr(ini_name, sizeof (ini_name), + (uintptr_t)ist.ist_initiator_name) == -1)) { + strcpy(ini_name, "N/A"); + } + + /* + * Read target name if available + */ + if ((ist.ist_target_name == NULL) || + (mdb_readstr(tgt_name, sizeof (tgt_name), + (uintptr_t)ist.ist_target_name) == -1)) { + strcpy(tgt_name, "N/A"); + } + + mdb_printf("Session %p\n", addr); + mdb_printf("%16s: %d\n", "State", + ist.ist_state); + mdb_printf("%16s: %d\n", "Last State", + ist.ist_last_state); + mdb_printf("%16s: %d\n", "FFP Connections", + ist.ist_ffp_conn_count); + mdb_printf("%16s: %02x%02x%02x%02x%02x%02x\n", "ISID", + ist.ist_isid[0], ist.ist_isid[1], ist.ist_isid[2], + ist.ist_isid[3], ist.ist_isid[4], ist.ist_isid[5]); + mdb_printf("%16s: 0x%04x\n", "TSIH", + ist.ist_tsih); + mdb_printf("%16s: %s\n", "Initiator IQN", + ini_name); + mdb_printf("%16s: %s\n", "Target IQN", + tgt_name); + mdb_printf("%16s: %08x\n", "ExpCmdSN", + ist.ist_expcmdsn); + mdb_printf("%16s: %08x\n", "MaxCmdSN", + ist.ist_maxcmdsn); + } else { + /* Print session data */ + if (idc->idc_header) { + mdb_printf("%%-?s %10s %-12s %-6s%\n", + "iscsit_sess_t", "State/Conn", "ISID", + "TSIH"); + } + mdb_printf("%?p %4d/%-4d %02x%02x%02x%02x%02x%02x " + "0x%04x\n", addr, + ist.ist_state, ist.ist_ffp_conn_count, + ist.ist_isid[0], ist.ist_isid[1], ist.ist_isid[2], + ist.ist_isid[3], ist.ist_isid[4], ist.ist_isid[5], + ist.ist_tsih); + } + idc->idc_header = 0; + } + + idc->idc_verbose = 0; + + /* + * Print states if requested + */ + if (states) { + states_addr = addr + offsetof(iscsit_sess_t, ist_state_audit); + + (void) mdb_inc_indent(4); + mdb_printf("State History:\n"); + if (iscsi_sm_audit_impl(states_addr) != DCMD_OK) + return (DCMD_ERR); + + /* Don't print state history for child objects */ + idc->u.child.idc_states = 0; + (void) mdb_dec_indent(4); + } + + /* + * Print refcnt audit data if requested + */ + if (rc_audit) { + (void) mdb_inc_indent(4); + mdb_printf("Reference History:\n"); + rc_addr = addr + + offsetof(iscsit_sess_t, ist_refcnt); + if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) + return (DCMD_ERR); + + /* Don't print audit data for child objects */ + idc->u.child.idc_rc_audit = 0; + (void) mdb_dec_indent(4); + } + + /* Any child objects to walk? */ + if (idc->u.child.idc_conn || idc->u.child.idc_task || + idc->u.child.idc_buffer) { + /* Walk conn list */ + idc->idc_header = 1; + (void) mdb_inc_indent(4); + list_addr = addr + offsetof(iscsit_sess_t, ist_conn_list); + if (mdb_pwalk("list", iscsi_sess_conn_walk_cb, idc, + list_addr) == -1) { + mdb_warn("session conn list walk failed"); + (void) mdb_dec_indent(4); + return (DCMD_ERR); + } + (void) mdb_dec_indent(4); + idc->idc_header = 0; + } + + idc->idc_verbose = verbose; + idc->u.child.idc_states = states; + idc->u.child.idc_rc_audit = rc_audit; + + return (DCMD_OK); +} + +static int +iscsi_conn_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) +{ + uintptr_t idm_global_addr, states_addr, rc_addr; + uintptr_t task_addr, task_ptr; + GElf_Sym sym; + idm_task_t idt; + idm_conn_t ic; + char *conn_type; + int task_idx; + char laddr[PORTAL_STR_LEN]; + char raddr[PORTAL_STR_LEN]; + int verbose, states, rc_audit; + + /* + * Get pointer to task table + */ + + if (mdb_lookup_by_name("idm", &sym) == -1) { + mdb_warn("failed to find symbol 'idm'"); + return (DCMD_ERR); + } + + idm_global_addr = (uintptr_t)sym.st_value; + + if (mdb_vread(&task_ptr, sizeof (uintptr_t), + idm_global_addr + offsetof(idm_global_t, idm_taskid_table)) != + sizeof (uintptr_t)) { + mdb_warn("Failed to read address of task table"); + return (DCMD_ERR); + } + + /* + * Read idm_conn_t + */ + if (mdb_vread(&ic, sizeof (idm_conn_t), addr) != sizeof (idm_conn_t)) { + return (DCMD_ERR); + } + conn_type = (ic.ic_conn_type == CONN_TYPE_INI) ? "Ini" : + (ic.ic_conn_type == CONN_TYPE_TGT) ? "Tgt" : "Unk"; + + /* + * Brief output + * + * idm_conn_t pointer + * idm_conn_t.ic_conn_type + * idm_conn_t.ic_statet+idm_conn_t.ic_ffp + */ + + verbose = idc->idc_verbose; + states = idc->u.child.idc_states; + rc_audit = idc->u.child.idc_rc_audit; + + if (idc->u.child.idc_conn) { + if (idc->idc_verbose) { + mdb_printf("IDM Conn %p\n", addr); + if (ic.ic_conn_type == CONN_TYPE_TGT) { + iscsi_print_iscsit_conn_data(&ic); + } else { + iscsi_print_idm_conn_data(&ic); + } + } else { + /* Print connection data */ + if (idc->idc_header) { + mdb_printf("%%-?s %-6s %-10s %12s%\n", + "idm_conn_t", "Type", "Transport", + "State/FFP"); + } + mdb_printf("%?p %-6s %-10s %6d/%-6d\n", addr, conn_type, + (ic.ic_transport_type == + IDM_TRANSPORT_TYPE_ISER) ? "ISER_IB" : + (ic.ic_transport_type == + IDM_TRANSPORT_TYPE_SOCKETS) ? "SOCKETS" : + "N/A", + ic.ic_state, ic.ic_ffp); + if (idc->u.child.idc_print_ip) { + sa_to_str(&ic.ic_laddr, laddr); + sa_to_str(&ic.ic_raddr, raddr); + mdb_printf(" L%s R%s\n", + laddr, raddr); + } + } + } + idc->idc_header = 0; + + idc->idc_verbose = 0; + + /* + * Print states if requested + */ + if (states) { + states_addr = addr + offsetof(idm_conn_t, ic_state_audit); + + (void) mdb_inc_indent(4); + mdb_printf("State History:\n"); + if (iscsi_sm_audit_impl(states_addr) != DCMD_OK) + return (DCMD_ERR); + + /* Don't print state history for child objects */ + idc->u.child.idc_states = 0; + (void) mdb_dec_indent(4); + } + + /* + * Print refcnt audit data if requested + */ + if (rc_audit) { + (void) mdb_inc_indent(4); + mdb_printf("Reference History:\n"); + rc_addr = addr + offsetof(idm_conn_t, ic_refcnt); + if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) + return (DCMD_ERR); + + /* Don't print audit data for child objects */ + idc->u.child.idc_rc_audit = 0; + (void) mdb_dec_indent(4); + } + + task_idx = 0; + + /* Any child objects to walk? */ + if (idc->u.child.idc_task || idc->u.child.idc_buffer) { + idc->idc_header = 1; + while (task_idx < IDM_TASKIDS_MAX) { + + /* + * Read the next idm_task_t + */ + + if (mdb_vread(&task_addr, sizeof (uintptr_t), + task_ptr) != sizeof (uintptr_t)) { + mdb_warn("Failed to read task pointer"); + return (DCMD_ERR); + } + + if (task_addr == NULL) { + task_ptr += sizeof (uintptr_t); + task_idx++; + continue; + } + + if (mdb_vread(&idt, sizeof (idm_task_t), task_addr) + != sizeof (idm_task_t)) { + mdb_warn("Failed to read task pointer"); + return (DCMD_ERR); + } + + if (((uintptr_t)idt.idt_ic == addr) && + (idt.idt_state != TASK_IDLE)) { + (void) mdb_inc_indent(4); + if (iscsi_i_task_impl(&idt, task_addr, idc) + == -1) { + mdb_warn("Failed to walk connection " + "task tree"); + (void) mdb_dec_indent(4); + return (DCMD_ERR); + } + (void) mdb_dec_indent(4); + } + + task_ptr += sizeof (uintptr_t); + task_idx++; + } + idc->idc_header = 0; + } + + idc->idc_verbose = verbose; + idc->u.child.idc_states = states; + idc->u.child.idc_rc_audit = rc_audit; + + return (DCMD_OK); +} + +static void +iscsi_print_iscsit_conn_data(idm_conn_t *ic) +{ + iscsit_conn_t ict; + char *csg; + char *nsg; + + iscsi_print_idm_conn_data(ic); + + if (mdb_vread(&ict, sizeof (iscsit_conn_t), + (uintptr_t)ic->ic_handle) != sizeof (iscsit_conn_t)) { + mdb_printf("**Failed to read conn private data\n"); + return; + } + + if (ict.ict_login_sm.icl_login_state != ILS_LOGIN_DONE) { + switch (ict.ict_login_sm.icl_login_csg) { + case ISCSI_SECURITY_NEGOTIATION_STAGE: + csg = "Security"; + break; + case ISCSI_OP_PARMS_NEGOTIATION_STAGE: + csg = "Operational"; + break; + case ISCSI_FULL_FEATURE_PHASE: + csg = "FFP"; + break; + default: + csg = "Unknown"; + } + switch (ict.ict_login_sm.icl_login_nsg) { + case ISCSI_SECURITY_NEGOTIATION_STAGE: + nsg = "Security"; + break; + case ISCSI_OP_PARMS_NEGOTIATION_STAGE: + nsg = "Operational"; + break; + case ISCSI_FULL_FEATURE_PHASE: + nsg = "FFP"; + break; + default: + nsg = "Unknown"; + } + mdb_printf("%20s: %d\n", "Login State", + ict.ict_login_sm.icl_login_state); + mdb_printf("%20s: %d\n", "Login Last State", + ict.ict_login_sm.icl_login_last_state); + mdb_printf("%20s: %s\n", "CSG", csg); + mdb_printf("%20s: %s\n", "NSG", nsg); + mdb_printf("%20s: %d\n", "Transit", + ict.ict_login_sm.icl_login_transit >> 7); + mdb_printf("%20s: %p\n", "Request nvlist", + ict.ict_login_sm.icl_request_nvlist); + mdb_printf("%20s: %p\n", "Response nvlist", + ict.ict_login_sm.icl_response_nvlist); + mdb_printf("%20s: %p\n", "Negotiated nvlist", + ict.ict_login_sm.icl_negotiated_values); + if (ict.ict_login_sm.icl_login_state == ILS_LOGIN_ERROR) { + mdb_printf("%20s: 0x%02x\n", "Error Class", + ict.ict_login_sm.icl_login_resp_err_class); + mdb_printf("%20s: 0x%02x\n", "Error Detail", + ict.ict_login_sm.icl_login_resp_err_detail); + } + } + mdb_printf("%20s: 0x%04x\n", "CID", ict.ict_cid); + mdb_printf("%20s: 0x%08x\n", "StatSN", ict.ict_statsn); +} + +static void +iscsi_print_idm_conn_data(idm_conn_t *ic) +{ + char laddr[PORTAL_STR_LEN]; + char raddr[PORTAL_STR_LEN]; + + sa_to_str(&ic->ic_laddr, laddr); + sa_to_str(&ic->ic_raddr, raddr); + + mdb_printf("%20s: %s\n", "Conn Type", + ((ic->ic_conn_type == CONN_TYPE_TGT) ? "Target" : + ((ic->ic_conn_type == CONN_TYPE_INI) ? "Initiator" : + "Unknown"))); + if (ic->ic_conn_type == CONN_TYPE_TGT) { + mdb_printf("%20s: %p\n", "Svc. Binding", + ic->ic_svc_binding); + } + mdb_printf("%20s: %s\n", "Transport", + (ic->ic_transport_type == IDM_TRANSPORT_TYPE_ISER) ? "ISER_IB" : + (ic->ic_transport_type == IDM_TRANSPORT_TYPE_SOCKETS) ? "SOCKETS" : + "N/A"); + + mdb_printf("%20s: %s\n", "Local IP", laddr); + mdb_printf("%20s: %s\n", "Remote IP", raddr); + mdb_printf("%20s: %d\n", "State", + ic->ic_state); + mdb_printf("%20s: %d\n", "Last State", + ic->ic_last_state); + mdb_printf("%20s: %d %s\n", "Refcount", + ic->ic_refcnt.ir_refcnt, + (ic->ic_refcnt.ir_waiting == REF_NOWAIT) ? "" : + ((ic->ic_refcnt.ir_waiting == REF_WAIT_SYNC) ? "REF_WAIT_SYNC" : + ((ic->ic_refcnt.ir_waiting == REF_WAIT_ASYNC) ? "REF_WAIT_ASYNC" : + "UNKNOWN"))); +} + +static int +iscsi_i_task_impl(idm_task_t *idt, uintptr_t addr, iscsi_dcmd_ctrl_t *idc) +{ + uintptr_t list_addr, rc_addr; + idm_conn_type_t conn_type; + int verbose, states, rc_audit; + + conn_type = idm_conn_type((uintptr_t)idt->idt_ic); + + verbose = idc->idc_verbose; + states = idc->u.child.idc_states; + rc_audit = idc->u.child.idc_rc_audit; + + if (idc->u.child.idc_task) { + if (verbose) { + mdb_printf("Task %p\n", addr); + (void) mdb_inc_indent(2); + if (conn_type == CONN_TYPE_TGT) { + iscsi_print_iscsit_task_data(idt); + } + (void) mdb_dec_indent(2); + } else { + /* Print task data */ + if (idc->idc_header) { + mdb_printf( + "%%-?s %-?s %-8s %-8s %-8s%\n", + "Tasks/Active:", "Private", + "Data SN", "Exp SN", + (conn_type == CONN_TYPE_TGT ? "TTT" : + (conn_type == CONN_TYPE_INI ? "ITT" : + "TT")), "Handle"); + } + mdb_printf("%?p %?p %08x %08x %08x %08x\n", addr, + idt->idt_private, idt->idt_exp_datasn, + idt->idt_exp_rttsn, idt->idt_tt, + idt->idt_client_handle); + } + } + idc->idc_header = 0; + idc->idc_verbose = 0; + + /* + * Print states if requested + */ +#if 0 + if (states) { + states_addr = addr + offsetof(idm_task_t, idt_state_audit); + + (void) mdb_inc_indent(4); + mdb_printf("State History:\n"); + if (iscsi_sm_audit_impl(states_addr) != DCMD_OK) + return (DCMD_ERR); + + /* Don't print state history for child objects */ + idc->u.child.idc_states = 0; + (void) mdb_dec_indent(4); + } +#endif + + /* + * Print refcnt audit data if requested + */ + if (rc_audit) { + (void) mdb_inc_indent(4); + mdb_printf("Reference History:\n"); + rc_addr = addr + + offsetof(idm_task_t, idt_refcnt); + if (iscsi_refcnt_impl(rc_addr) != DCMD_OK) + return (DCMD_ERR); + + /* Don't print audit data for child objects */ + idc->u.child.idc_rc_audit = 0; + (void) mdb_dec_indent(4); + } + + + /* Buffers are leaf objects */ + if (idc->u.child.idc_buffer) { + /* Walk in buffer list */ + (void) mdb_inc_indent(2); + mdb_printf("In buffers:\n"); + idc->idc_header = 1; + (void) mdb_inc_indent(2); + list_addr = addr + offsetof(idm_task_t, idt_inbufv); + if (mdb_pwalk("list", iscsi_buffer_walk_cb, idc, list_addr) == + -1) { + mdb_warn("list walk failed for task in buffers"); + (void) mdb_dec_indent(4); + return (DCMD_ERR); + } + (void) mdb_dec_indent(2); + /* Walk out buffer list */ + mdb_printf("Out buffers:\n"); + idc->idc_header = 1; + (void) mdb_inc_indent(2); + list_addr = addr + offsetof(idm_task_t, idt_outbufv); + if (mdb_pwalk("list", iscsi_buffer_walk_cb, idc, list_addr) == + -1) { + mdb_warn("list walk failed for task out buffers\n"); + (void) mdb_dec_indent(2); + return (DCMD_ERR); + } + (void) mdb_dec_indent(4); + } + + idc->idc_verbose = verbose; + idc->u.child.idc_states = states; + idc->u.child.idc_rc_audit = rc_audit; + + return (DCMD_OK); +} + +static int +iscsi_task_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) +{ + idm_task_t idt; + + /* + * Read idm_conn_t + */ + if (mdb_vread(&idt, sizeof (idm_task_t), addr) != sizeof (idm_task_t)) { + return (DCMD_ERR); + } + + return (iscsi_i_task_impl(&idt, addr, idc)); +} + +#define ISCSI_CDB_INDENT 16 + +static void +iscsi_print_iscsit_task_data(idm_task_t *idt) +{ + iscsit_task_t itask; + boolean_t good_scsi_task = B_TRUE; + scsi_task_t scsi_task; + + if (mdb_vread(&itask, sizeof (iscsit_task_t), + (uintptr_t)idt->idt_private) != sizeof (iscsit_task_t)) { + mdb_printf("**Failed to read idt_private data\n"); + return; + } + + if (mdb_vread(&scsi_task, sizeof (scsi_task_t), + (uintptr_t)itask.it_stmf_task) != sizeof (scsi_task_t)) { + good_scsi_task = B_FALSE; + } + + mdb_printf("%20s: %p/%p/%p%s\n", + "iscsit/STMF/LU", idt->idt_private, + itask.it_stmf_task, good_scsi_task ? scsi_task.task_lu_private : 0, + good_scsi_task ? "" : "**"); + if (good_scsi_task) { + mdb_printf("%20s: %08x/%08x\n", "ITT/TTT", + itask.it_itt, itask.it_ttt); + mdb_printf("%20s: %08x\n", "CmdSN", + itask.it_cmdsn); + mdb_printf("%20s: %02x %02x %02x %02x %02x %02x %02x %02x\n", + "LU number", + scsi_task.task_lun_no[0], scsi_task.task_lun_no[1], + scsi_task.task_lun_no[2], scsi_task.task_lun_no[3], + scsi_task.task_lun_no[4], scsi_task.task_lun_no[5], + scsi_task.task_lun_no[6], scsi_task.task_lun_no[7]); + mdb_printf(" CDB (%d bytes):\n", + scsi_task.task_cdb_length); + (void) mdb_inc_indent(ISCSI_CDB_INDENT); + if (mdb_dumpptr((uintptr_t)scsi_task.task_cdb, + scsi_task.task_cdb_length, + MDB_DUMP_RELATIVE | MDB_DUMP_TRIM | + MDB_DUMP_GROUP(1), + (mdb_dumpptr_cb_t)mdb_vread, NULL)) { + mdb_printf("** Invalid CDB addr (%p)\n", + scsi_task.task_cdb); + } + (void) mdb_dec_indent(ISCSI_CDB_INDENT); + mdb_printf("%20s: %d/%d\n", "STMF cur/max bufs", + scsi_task.task_cur_nbufs, + scsi_task.task_max_nbufs); + mdb_printf("%20s: 0x%08x/0x%08x/0x%08x\n", "Bytes Exp/Cmd/Done", + scsi_task.task_expected_xfer_length, + scsi_task.task_cmd_xfer_length, + scsi_task.task_nbytes_transferred); + mdb_printf("%20s: 0x%x/0x%x\n", "TX-ini start/done", + idt->idt_tx_to_ini_start, + idt->idt_tx_to_ini_done); + mdb_printf("%20s: 0x%x/0x%x\n", "RX-ini start/done", + idt->idt_rx_from_ini_start, + idt->idt_rx_from_ini_done); + } +} + +static int +iscsi_buffer_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc) +{ + idm_buf_t idb; + + /* + * Read idm_buf_t + */ + if (mdb_vread(&idb, sizeof (idm_buf_t), addr) != sizeof (idm_buf_t)) { + return (DCMD_ERR); + } + + + if (idc->idc_header) { + mdb_printf("%%-?s %?s/%-8s %8s %8s %8s%\n", + "idm_buf_t", "Mem Rgn", "Length", + "Rel Off", "Xfer Len", "Exp. Off"); + } + idc->idc_header = 0; + + /* Print buffer data */ + mdb_printf("%?p %?p/%08x %8x %8x %08x\n", addr, + idb.idb_buf, idb.idb_buflen, + idb.idb_bufoffset, idb.idb_xfer_len, + idb.idb_exp_offset); + + + /* Buffers are leaf objects */ + + return (DCMD_OK); +} + +static int +iscsi_refcnt_impl(uintptr_t addr) +{ + idm_refcnt_t refcnt; + refcnt_audit_buf_t *anb; + int ctr; + + /* + * Print refcnt info + */ + if (mdb_vread(&refcnt, sizeof (idm_refcnt_t), addr) != + sizeof (idm_refcnt_t)) { + return (DCMD_ERR); + } + + anb = &refcnt.ir_audit_buf; + + ctr = anb->anb_max_index + 1; + anb->anb_index--; + anb->anb_index &= anb->anb_max_index; + + while (ctr) { + refcnt_audit_record_t *anr; + + anr = anb->anb_records + anb->anb_index; + + if (anr->anr_depth) { + char c[MDB_SYM_NAMLEN]; + GElf_Sym sym; + int i; + + mdb_printf("\nRefCnt: %u\t", anr->anr_refcnt); + + for (i = 0; i < anr->anr_depth; i++) { + if (mdb_lookup_by_addr(anr->anr_stack[i], + MDB_SYM_FUZZY, c, sizeof (c), + &sym) == -1) { + continue; + } + mdb_printf("%s+0x%1x", c, + anr->anr_stack[i] - + (uintptr_t)sym.st_value); + ++i; + break; + } + + while (i < anr->anr_depth) { + if (mdb_lookup_by_addr(anr->anr_stack[i], + MDB_SYM_FUZZY, c, sizeof (c), + &sym) == -1) { + ++i; + continue; + } + mdb_printf("\n\t\t%s+0x%1x", c, + anr->anr_stack[i] - + (uintptr_t)sym.st_value); + ++i; + } + mdb_printf("\n"); + } + anb->anb_index--; + anb->anb_index &= anb->anb_max_index; + ctr--; + } + + return (DCMD_OK); +} + +static int +iscsi_sm_audit_impl(uintptr_t addr) +{ + sm_audit_buf_t audit_buf; + int ctr; + const char *event_name; + const char *state_name; + const char *new_state_name; + char ts_string[40]; + /* + * Print refcnt info + */ + if (mdb_vread(&audit_buf, sizeof (sm_audit_buf_t), addr) != + sizeof (sm_audit_buf_t)) { + return (DCMD_ERR); + } + + ctr = audit_buf.sab_max_index + 1; + audit_buf.sab_index++; + audit_buf.sab_index &= audit_buf.sab_max_index; + + while (ctr) { + sm_audit_record_t *sar; + + sar = audit_buf.sab_records + audit_buf.sab_index; + + iscsi_format_timestamp(ts_string, 40, &sar->sar_timestamp); + + switch (sar->sar_type) { + case SAR_STATE_EVENT: + switch (sar->sar_sm_type) { + case SAS_IDM_CONN: + state_name = + iscsi_idm_conn_state(sar->sar_state); + event_name = + iscsi_idm_conn_event(sar->sar_event); + break; + case SAS_ISCSIT_TGT: + state_name = + iscsi_iscsit_tgt_state(sar->sar_state); + event_name = + iscsi_iscsit_tgt_event(sar->sar_event); + break; + case SAS_ISCSIT_SESS: + state_name = + iscsi_iscsit_sess_state(sar->sar_state); + event_name = + iscsi_iscsit_sess_event(sar->sar_event); + break; + case SAS_ISCSIT_LOGIN: + state_name = + iscsi_iscsit_login_state(sar->sar_state); + event_name = + iscsi_iscsit_login_event(sar->sar_event); + break; + default: + state_name = event_name = "N/A"; + break; + } + mdb_printf("%s|%s (%d)\n\t%9s %s (%d) %p\n", + ts_string, state_name, sar->sar_state, + "Event", event_name, + sar->sar_event, sar->sar_event_info); + + break; + case SAR_STATE_CHANGE: + switch (sar->sar_sm_type) { + case SAS_IDM_CONN: + state_name = + iscsi_idm_conn_state(sar->sar_state); + new_state_name = + iscsi_idm_conn_state(sar->sar_new_state); + break; + case SAS_IDM_TASK: + state_name = + iscsi_idm_task_state(sar->sar_state); + new_state_name = + iscsi_idm_task_state(sar->sar_new_state); + break; + case SAS_ISCSIT_TGT: + state_name = + iscsi_iscsit_tgt_state(sar->sar_state); + new_state_name = + iscsi_iscsit_tgt_state(sar->sar_new_state); + break; + case SAS_ISCSIT_SESS: + state_name = + iscsi_iscsit_sess_state(sar->sar_state); + new_state_name = + iscsi_iscsit_sess_state(sar->sar_new_state); + break; + case SAS_ISCSIT_LOGIN: + state_name = + iscsi_iscsit_login_state(sar->sar_state); + new_state_name = + iscsi_iscsit_login_state( + sar->sar_new_state); + break; + default: + break; + } + mdb_printf("%s|%s (%d)\n\t%9s %s (%d)\n", + ts_string, state_name, sar->sar_state, + "New State", new_state_name, sar->sar_new_state); + default: + state_name = new_state_name = "N/A"; + break; + } + + audit_buf.sab_index++; + audit_buf.sab_index &= audit_buf.sab_max_index; + ctr--; + } + + return (DCMD_OK); +} + +static const char * +iscsi_idm_conn_event(int event) +{ + const char *name = "N/A"; + + event = (event > CE_MAX_EVENT) ? CE_MAX_EVENT : event; + name = idm_ce_name[event]; + + return (name); +} + +static const char * +iscsi_iscsit_tgt_event(int event) +{ + const char *name = "N/A"; + + event = (event > TE_MAX_EVENT) ? TE_MAX_EVENT : event; + name = iscsit_te_name[event]; + + return (name); +} + +static const char * +iscsi_iscsit_sess_event(int event) +{ + const char *name = "N/A"; + + event = (event > SE_MAX_EVENT) ? SE_MAX_EVENT : event; + name = iscsit_se_name[event]; + + return (name); +} + +static const char * +iscsi_iscsit_login_event(int event) +{ + const char *name = "N/A"; + + event = (event > ILE_MAX_EVENT) ? ILE_MAX_EVENT : event; + name = iscsit_ile_name[event]; + + return (name); +} + +static const char * +iscsi_idm_conn_state(int state) +{ + const char *name = "N/A"; + + state = (state > CS_MAX_STATE) ? CS_MAX_STATE : state; + name = idm_cs_name[state]; + + return (name); +} + +/*ARGSUSED*/ +static const char * +iscsi_idm_task_state(int state) +{ + const char *name = "N/A"; + return (name); +} + +static const char * +iscsi_iscsit_tgt_state(int state) +{ + const char *name = "N/A"; + + state = (state > TS_MAX_STATE) ? TS_MAX_STATE : state; + name = iscsit_ts_name[state]; + + return (name); +} + +static const char * +iscsi_iscsit_sess_state(int state) +{ + const char *name = "N/A"; + + state = (state > SS_MAX_STATE) ? SS_MAX_STATE : state; + name = iscsit_ss_name[state]; + + return (name); +} + +static const char * +iscsi_iscsit_login_state(int state) +{ + const char *name = "N/A"; + + state = (state > ILS_MAX_STATE) ? ILS_MAX_STATE : state; + name = iscsit_ils_name[state]; + + return (name); +} + + + +/* + * Retrieve connection type given a kernel address + */ +static idm_conn_type_t +idm_conn_type(uintptr_t addr) +{ + idm_conn_type_t result = 0; /* Unknown */ + uintptr_t idm_conn_type_addr; + + idm_conn_type_addr = addr + offsetof(idm_conn_t, ic_conn_type); + (void) mdb_vread(&result, sizeof (result), idm_conn_type_addr); + + return (result); +} + +/* + * Convert a sockaddr to the string representation, suitable for + * storing in an nvlist or printing out in a list. + */ +static int +sa_to_str(struct sockaddr_storage *sa, char *buf) +{ + char pbuf[7]; + const char *bufp; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + uint16_t port; + + if (!sa || !buf) { + return (EINVAL); + } + + buf[0] = '\0'; + + if (sa->ss_family == AF_INET) { + sin = (struct sockaddr_in *)sa; + bufp = inet_ntop(AF_INET, + (const void *)&(sin->sin_addr.s_addr), + buf, PORTAL_STR_LEN); + if (bufp == NULL) { + return (-1); + } + mdb_nhconvert(&port, &sin->sin_port, sizeof (uint16_t)); + } else if (sa->ss_family == AF_INET6) { + strlcat(buf, "[", sizeof (buf)); + sin6 = (struct sockaddr_in6 *)sa; + bufp = inet_ntop(AF_INET6, + (const void *)&sin6->sin6_addr.s6_addr, + &buf[1], PORTAL_STR_LEN - 1); + if (bufp == NULL) { + return (-1); + } + strlcat(buf, "]", PORTAL_STR_LEN); + mdb_nhconvert(&port, &sin->sin_port, sizeof (uint16_t)); + } else { + return (EINVAL); + } + + + mdb_snprintf(pbuf, sizeof (pbuf), ":%u", port); + strlcat(buf, pbuf, PORTAL_STR_LEN); + + return (0); +} + + +static void +iscsi_format_timestamp(char *ts_str, int strlen, timespec_t *ts) +{ + mdb_snprintf(ts_str, strlen, "%Y:%03d:%03d:%03d", ts->tv_sec, + (ts->tv_nsec / 1000000) % 1000, (ts->tv_nsec / 1000) % 1000, + ts->tv_nsec % 1000); +} + +/* + * Help information for the iscsi_isns dcmd + */ +static void +iscsi_isns_help(void) +{ + mdb_printf("iscsi_isns:\n"); + mdb_inc_indent(4); + mdb_printf("-e: Print ESI information\n"); + mdb_printf("-p: Print portal information\n"); + mdb_printf("-s: Print iSNS server information\n"); + mdb_printf("-t: Print target information\n"); + mdb_printf("-v: Add verbosity to the other options' output\n"); + mdb_dec_indent(4); +} + +/* ARGSUSED */ +static int +iscsi_isns_esi_cb(uintptr_t addr, const void *walker_data, void *data) +{ + iscsi_dcmd_ctrl_t *idc = (iscsi_dcmd_ctrl_t *)data; + isns_esi_tinfo_t tinfo; + + if (mdb_vread(&tinfo, sizeof (isns_esi_tinfo_t), addr) != + sizeof (isns_esi_tinfo_t)) { + return (WALK_ERR); + } + + mdb_printf("ESI portal : 0x%p\n", tinfo.esi_portal); + if (idc->idc_verbose) { + mdb_inc_indent(4); + iscsi_isns_portal_cb((uintptr_t)tinfo.esi_portal, NULL, data); + mdb_dec_indent(4); + } + mdb_printf("ESI thread/thr did : 0x%p / %d\n", tinfo.esi_thread, + tinfo.esi_thread_did); + mdb_printf("ESI sonode : 0x%p\n", tinfo.esi_so); + mdb_printf("ESI port : %d\n", tinfo.esi_port); + mdb_printf("ESI thread running : %s\n", + (tinfo.esi_thread_running) ? "Yes" : "No"); + if (!tinfo.esi_thread_running) { + mdb_printf("ESI thread failed : %s\n", + (tinfo.esi_thread_failed) ? "Yes" : "No"); + } + mdb_printf("ESI registered : %s\n\n", + (tinfo.esi_registered) ? "Yes" : "No"); + + return (WALK_NEXT); +} + +static int +iscsi_isns_esi(iscsi_dcmd_ctrl_t *idc) +{ + GElf_Sym sym; + uintptr_t esi_list; + + if (mdb_lookup_by_name("esi_list", &sym) == -1) { + mdb_warn("failed to find symbol 'esi_list'"); + return (DCMD_ERR); + } + + esi_list = (uintptr_t)sym.st_value; + idc->idc_header = 1; + + if (mdb_pwalk("list", iscsi_isns_esi_cb, idc, esi_list) == -1) { + mdb_warn("avl walk failed for esi_list"); + return (DCMD_ERR); + } + + return (0); +} + +/* ARGSUSED */ +static int +iscsi_isns_portal_cb(uintptr_t addr, const void *walker_data, void *data) +{ + iscsi_dcmd_ctrl_t *idc = (iscsi_dcmd_ctrl_t *)data; + isns_portal_list_t portal; + char portal_addr[PORTAL_STR_LEN]; + struct sockaddr_storage *ss; + + if (mdb_vread(&portal, sizeof (isns_portal_list_t), addr) != + sizeof (isns_portal_list_t)) { + return (WALK_ERR); + } + + ss = &portal.portal_addr; + sa_to_str(ss, portal_addr); + mdb_printf("Portal IP address "); + + if (ss->ss_family == AF_INET) { + mdb_printf("(v4): %s", portal_addr); + } else { + mdb_printf("(v6): %s", portal_addr); + } + + if (portal.portal_iscsit == NULL) { + mdb_printf(" (Default portal)\n"); + } else { + mdb_printf("\n"); + } + + if ((portal.portal_iscsit != NULL) && (idc->idc_verbose)) { + iscsi_portal_impl((uintptr_t)portal.portal_iscsit, idc); + } + + mdb_printf("Portal ESI info: 0x%p\n\n", portal.portal_esi); + + return (WALK_NEXT); +} + +static int +iscsi_isns_portals(iscsi_dcmd_ctrl_t *idc) +{ + GElf_Sym sym; + uintptr_t portal_list; + + if (mdb_lookup_by_name("portal_list", &sym) == -1) { + mdb_warn("failed to find symbol 'portal_list'"); + return (DCMD_ERR); + } + + portal_list = (uintptr_t)sym.st_value; + idc->idc_header = 1; + + if (mdb_pwalk("list", iscsi_isns_portal_cb, idc, portal_list) == -1) { + mdb_warn("avl walk failed for portal_list"); + return (DCMD_ERR); + } + + return (0); +} + +/* ARGSUSED */ +static int +iscsi_isns_targets_cb(uintptr_t addr, const void *walker_data, void *data) +{ + iscsi_dcmd_ctrl_t *idc = (iscsi_dcmd_ctrl_t *)data; + isns_target_t itarget; + int rc = 0; + + if (mdb_vread(&itarget, sizeof (isns_target_t), addr) != + sizeof (isns_target_t)) { + return (WALK_ERR); + } + + idc->idc_header = 1; + + mdb_printf("Target: %p\n", itarget.target); + mdb_inc_indent(4); + mdb_printf("Registered: %s\n", + (itarget.target_registered) ? "Yes" : "No"); + + rc = iscsi_tgt_impl((uintptr_t)itarget.target, idc); + + mdb_dec_indent(4); + + if (rc == DCMD_OK) { + return (WALK_NEXT); + } + + return (WALK_ERR); +} + +static int +iscsi_isns_targets(iscsi_dcmd_ctrl_t *idc) +{ + GElf_Sym sym; + uintptr_t isns_target_list; + + if (mdb_lookup_by_name("isns_target_list", &sym) == -1) { + mdb_warn("failed to find symbol 'isns_target_list'"); + return (DCMD_ERR); + } + + isns_target_list = (uintptr_t)sym.st_value; + idc->idc_header = 1; + idc->u.child.idc_tgt = 1; + + if (mdb_pwalk("avl", iscsi_isns_targets_cb, idc, + isns_target_list) == -1) { + mdb_warn("avl walk failed for isns_target_list"); + return (DCMD_ERR); + } + + return (0); +} + +/* ARGSUSED */ +static int +iscsi_isns_servers_cb(uintptr_t addr, const void *walker_data, void *data) +{ + GElf_Sym sym; + iscsit_isns_svr_t server; + char server_addr[PORTAL_STR_LEN]; + struct sockaddr_storage *ss; + clock_t lbolt; + + if (mdb_vread(&server, sizeof (iscsit_isns_svr_t), addr) != + sizeof (iscsit_isns_svr_t)) { + return (WALK_ERR); + } + + if (mdb_lookup_by_name("lbolt", &sym) == -1) { + mdb_warn("failed to find symbol 'lbolt'"); + return (DCMD_ERR); + } + + if (mdb_vread(&lbolt, sizeof (clock_t), sym.st_value) != + sizeof (clock_t)) { + return (WALK_ERR); + } + + mdb_printf("iSNS server %p:\n", addr); + mdb_inc_indent(4); + ss = &server.svr_sa; + sa_to_str(ss, server_addr); + + mdb_printf("IP address "); + if (ss->ss_family == AF_INET) { + mdb_printf("(v4): %s\n", server_addr); + } else { + mdb_printf("(v6): %s\n", server_addr); + } + + mdb_printf("Last ESI message : %d seconds ago\n", + ((lbolt - server.svr_last_msg) / 100)); + mdb_printf("Client registered: %s\n", + (server.svr_registered) ? "Yes" : "No"); + mdb_dec_indent(4); + + return (WALK_ERR); +} + +static int +iscsi_isns_servers(iscsi_dcmd_ctrl_t *idc) +{ + uintptr_t iscsit_global_addr; + uintptr_t list_addr; + GElf_Sym sym; + + if (mdb_lookup_by_name("iscsit_global", &sym) == -1) { + mdb_warn("failed to find symbol 'iscsit_global'"); + return (DCMD_ERR); + } + + iscsit_global_addr = (uintptr_t)sym.st_value; + idc->idc_header = 1; + list_addr = iscsit_global_addr + + offsetof(iscsit_global_t, global_isns_cfg.isns_svrs); + + if (mdb_pwalk("list", iscsi_isns_servers_cb, idc, list_addr) == -1) { + mdb_warn("list walk failed for iSNS servers"); + return (DCMD_ERR); + } + + return (0); +} + +/* ARGSUSED */ +static int +iscsi_isns(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) +{ + iscsi_dcmd_ctrl_t idc; + int portals = 0, esi = 0, targets = 0, verbose = 0, servers = 0; + + if (flags & DCMD_ADDRSPEC) { + mdb_warn("iscsi_isns is only a global dcmd."); + return (DCMD_ERR); + } + + bzero(&idc, sizeof (idc)); + if (mdb_getopts(argc, argv, + 'e', MDB_OPT_SETBITS, TRUE, &esi, + 'p', MDB_OPT_SETBITS, TRUE, &portals, + 's', MDB_OPT_SETBITS, TRUE, &servers, + 't', MDB_OPT_SETBITS, TRUE, &targets, + 'v', MDB_OPT_SETBITS, TRUE, &verbose, + NULL) != argc) + return (DCMD_USAGE); + + if ((esi + portals + targets + servers) > 1) { + mdb_printf("Only one of e, p, s, and t must be provided"); + return (DCMD_ERR); + } + + if ((esi | portals | targets | servers) == 0) { + mdb_printf("Exactly one of e, p, s, or t must be provided"); + return (DCMD_ERR); + } + + idc.idc_verbose = verbose; + + if (esi) { + return (iscsi_isns_esi(&idc)); + } + + if (portals) { + return (iscsi_isns_portals(&idc)); + } + + if (servers) { + return (iscsi_isns_servers(&idc)); + } + + return (iscsi_isns_targets(&idc)); +} + +/* + * inet_ntop -- Convert an IPv4 or IPv6 address in binary form into + * printable form, and return a pointer to that string. Caller should + * provide a buffer of correct length to store string into. + * Note: this routine is kernel version of inet_ntop. It has similar + * format as inet_ntop() defined in rfc2553. But it does not do + * error handling operations exactly as rfc2553 defines. This function + * is used by kernel inet directory routines only for debugging. + * This inet_ntop() function, does not return NULL if third argument + * is NULL. The reason is simple that we don't want kernel to panic + * as the output of this function is directly fed to ipdbg macro. + * Instead it uses a local buffer for destination address for + * those calls which purposely pass NULL ptr for the destination + * buffer. This function is thread-safe when the caller passes a non- + * null buffer with the third argument. + */ +/* ARGSUSED */ + +#define OK_16PTR(p) (!((uintptr_t)(p) & 0x1)) +#if defined(__x86) +#define OK_32PTR(p) OK_16PTR(p) +#else +#define OK_32PTR(p) (!((uintptr_t)(p) & 0x3)) +#endif + +char * +inet_ntop(int af, const void *addr, char *buf, int addrlen) +{ + static char local_buf[PORTAL_STR_LEN]; + static char *err_buf1 = ""; + static char *err_buf2 = ""; + in6_addr_t *v6addr; + uchar_t *v4addr; + char *caddr; + + /* + * We don't allow thread unsafe inet_ntop calls, they + * must pass a non-null buffer pointer. For DEBUG mode + * we use the ASSERT() and for non-debug kernel it will + * silently allow it for now. Someday we should remove + * the static buffer from this function. + */ + + ASSERT(buf != NULL); + if (buf == NULL) + buf = local_buf; + buf[0] = '\0'; + + /* Let user know politely not to send NULL or unaligned addr */ + if (addr == NULL || !(OK_32PTR(addr))) { + return (err_buf1); + } + + +#define UC(b) (((int)b) & 0xff) + switch (af) { + case AF_INET: + ASSERT(addrlen >= INET_ADDRSTRLEN); + v4addr = (uchar_t *)addr; + (void) mdb_snprintf(buf, INET6_ADDRSTRLEN, + "%03d.%03d.%03d.%03d", + UC(v4addr[0]), UC(v4addr[1]), UC(v4addr[2]), UC(v4addr[3])); + return (buf); + + case AF_INET6: + ASSERT(addrlen >= INET6_ADDRSTRLEN); + v6addr = (in6_addr_t *)addr; + if (IN6_IS_ADDR_V4MAPPED(v6addr)) { + caddr = (char *)addr; + (void) mdb_snprintf(buf, INET6_ADDRSTRLEN, + "::ffff:%d.%d.%d.%d", + UC(caddr[12]), UC(caddr[13]), + UC(caddr[14]), UC(caddr[15])); + } else if (IN6_IS_ADDR_V4COMPAT(v6addr)) { + caddr = (char *)addr; + (void) mdb_snprintf(buf, INET6_ADDRSTRLEN, + "::%d.%d.%d.%d", + UC(caddr[12]), UC(caddr[13]), UC(caddr[14]), + UC(caddr[15])); + } else if (IN6_IS_ADDR_UNSPECIFIED(v6addr)) { + (void) mdb_snprintf(buf, INET6_ADDRSTRLEN, "::"); + } else { + convert2ascii(buf, v6addr); + } + return (buf); + + default: + return (err_buf2); + } +#undef UC +} + +/* + * + * v6 formats supported + * General format xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx + * The short hand notation :: is used for COMPAT addr + * Other forms : fe80::xxxx:xxxx:xxxx:xxxx + */ +static void +convert2ascii(char *buf, const in6_addr_t *addr) +{ + int hexdigits; + int head_zero = 0; + int tail_zero = 0; + /* tempbuf must be big enough to hold ffff:\0 */ + char tempbuf[6]; + char *ptr; + uint16_t out_addr_component; + uint16_t *addr_component; + size_t len; + boolean_t first = B_FALSE; + boolean_t med_zero = B_FALSE; + boolean_t end_zero = B_FALSE; + + addr_component = (uint16_t *)addr; + ptr = buf; + + /* First count if trailing zeroes higher in number */ + for (hexdigits = 0; hexdigits < 8; hexdigits++) { + if (*addr_component == 0) { + if (hexdigits < 4) + head_zero++; + else + tail_zero++; + } + addr_component++; + } + addr_component = (uint16_t *)addr; + if (tail_zero > head_zero && (head_zero + tail_zero) != 7) + end_zero = B_TRUE; + + for (hexdigits = 0; hexdigits < 8; hexdigits++) { + + /* if entry is a 0 */ + + if (*addr_component == 0) { + if (!first && *(addr_component + 1) == 0) { + if (end_zero && (hexdigits < 4)) { + *ptr++ = '0'; + *ptr++ = ':'; + } else { + /* + * address starts with 0s .. + * stick in leading ':' of pair + */ + if (hexdigits == 0) + *ptr++ = ':'; + /* add another */ + *ptr++ = ':'; + first = B_TRUE; + med_zero = B_TRUE; + } + } else if (first && med_zero) { + if (hexdigits == 7) + *ptr++ = ':'; + addr_component++; + continue; + } else { + *ptr++ = '0'; + *ptr++ = ':'; + } + addr_component++; + continue; + } + if (med_zero) + med_zero = B_FALSE; + + tempbuf[0] = '\0'; + mdb_nhconvert(&out_addr_component, addr_component, + sizeof (uint16_t)); + (void) mdb_snprintf(tempbuf, 6, "%x:", out_addr_component); + len = strlen(tempbuf); + bcopy(tempbuf, ptr, len); + ptr = ptr + len; + addr_component++; + } + *--ptr = '\0'; +} + + +/* + * MDB module linkage information: + * + * We declare a list of structures describing our dcmds, a list of structures + * describing our walkers and a function named _mdb_init to return a pointer + * to our module information. + */ +static const mdb_dcmd_t dcmds[] = { + { "iscsi_tgt", "[-agsctbSRv]", + "iSCSI target information", iscsi_tgt }, + { "iscsi_tpg", "[-v]", + "iSCSI target portal group information", iscsi_tpg }, + { "iscsi_sess", "[-abtvcSRIT]", + "iSCSI session information", iscsi_sess }, + { "iscsi_conn", "[-abtvSRIT]", + "iSCSI connection information", iscsi_conn }, + { "iscsi_task", "[-bSRv]", + "iSCSI task information", iscsi_task }, + { "iscsi_refcnt", "", + "Print audit informtion for idm_refcnt_t", iscsi_refcnt }, + { "iscsi_states", "", + "Dump events and state transitions recorded in an\t" + "\t\tidm_sm_audit_t structure", iscsi_states }, + { "iscsi_isns", "[-epstv]", + "Print iscsit iSNS information", iscsi_isns, iscsi_isns_help }, + { NULL } +}; + +/* + * No walkers for now. Initiator might need some since it doesn't use list_t + */ + +static const mdb_modinfo_t modinfo = { + MDB_API_VERSION, dcmds, NULL +}; + +const mdb_modinfo_t * +_mdb_init(void) +{ + return (&modinfo); +} diff --git a/usr/src/cmd/mdb/intel/amd64/idm/Makefile b/usr/src/cmd/mdb/intel/amd64/idm/Makefile new file mode 100644 index 000000000000..e1fc48fdfdb0 --- /dev/null +++ b/usr/src/cmd/mdb/intel/amd64/idm/Makefile @@ -0,0 +1,38 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +MODULE = idm.so +MDBTGT = kvm + +MODSRCS = idm.c + +ISCSITBASE = ../../../../../uts/common/io/comstar/port/iscsit + +include ../../../../Makefile.cmd +include ../../../../Makefile.cmd.64 +include ../../Makefile.amd64 +include ../../../Makefile.module + +CPPFLAGS += -I$(ISCSITBASE) diff --git a/usr/src/cmd/mdb/intel/ia32/idm/Makefile b/usr/src/cmd/mdb/intel/ia32/idm/Makefile new file mode 100644 index 000000000000..45777b1d0b29 --- /dev/null +++ b/usr/src/cmd/mdb/intel/ia32/idm/Makefile @@ -0,0 +1,37 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +MODULE = idm.so +MDBTGT = kvm + +MODSRCS = idm.c + +ISCSITBASE = ../../../../../uts/common/io/comstar/port/iscsit + +include ../../../../Makefile.cmd +include ../../Makefile.ia32 +include ../../../Makefile.module + +CPPFLAGS += -I$(ISCSITBASE) diff --git a/usr/src/cmd/mdb/sparc/v9/idm/Makefile b/usr/src/cmd/mdb/sparc/v9/idm/Makefile new file mode 100644 index 000000000000..cab142407e54 --- /dev/null +++ b/usr/src/cmd/mdb/sparc/v9/idm/Makefile @@ -0,0 +1,38 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +MODULE = idm.so +MDBTGT = kvm + +MODSRCS = idm.c + +ISCSITBASE = ../../../../../uts/common/io/comstar/port/iscsit + +include ../../../../Makefile.cmd +include ../../../../Makefile.cmd.64 +include ../../Makefile.sparcv9 +include ../../../Makefile.module + +CPPFLAGS += -I$(ISCSITBASE) diff --git a/usr/src/common/iscsi/base64.c b/usr/src/common/iscsi/base64.c new file mode 100644 index 000000000000..f4f336480e75 --- /dev/null +++ b/usr/src/common/iscsi/base64.c @@ -0,0 +1,255 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#ifdef _KERNEL +#include +#include +#else +#include +#include +#endif /* _KERNEL */ + +/* + * base64 decoding table (from uudecode.c) + */ +/* BEGIN CSTYLED */ + static char base64_decode_tab[] = { + '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', + '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', + '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', + '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', + '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377', + '\377', '\377', '\377', 62, '\377', '\377', '\377', 63, + 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, '\377', '\377', '\377', '\377', '\377', '\377', + '\377', 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, '\377', '\377', '\377', '\377', '\377', + '\377', 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, '\377', '\377', '\377', '\377', '\377' +}; +/* END CSTYLED */ + +/* true if the character is in the base64 encoding table */ +#define validbase64(c) (('A' <= (c) && (c) <= 'Z') || \ + ('a' <= (c) && (c) <= 'z') || \ + ('0' <= (c) && (c) <= '9') || \ + (c) == '+' || (c) == '/') + +static int +outdec64(unsigned char *out, unsigned char *chr, int num) +{ + + unsigned char char1, char2, char3, char4; + unsigned char *outptr = out; + int rc = 0; + + switch (num) { + case 0: + case 1: /* these are impossible */ + default: + break; + case 2: /* 2 base64 bytes == 1 decoded byte */ + char1 = base64_decode_tab[chr[0]] & 0xFF; + char2 = base64_decode_tab[chr[1]] & 0xFF; + *(outptr++) = ((char1 << 2) & 0xFC) | + ((char2 >> 4) & 0x03); + rc = 1; + break; + case 3: /* 3 base64 bytes == 2 decoded bytes */ + char1 = base64_decode_tab[chr[0]] & 0xFF; + char2 = base64_decode_tab[chr[1]] & 0xFF; + char3 = base64_decode_tab[chr[2]] & 0xFF; + *(outptr++) = ((char1 << 2) & 0xFC) | + ((char2 >> 4) & 0x03); + *(outptr++) = ((char2 << 4) & 0xF0) | + ((char3 >> 2) & 0x0F); + rc = 2; + break; + case 4: /* 4 base64 bytes == 3 decoded bytes */ + char1 = base64_decode_tab[chr[0]] & 0xFF; + char2 = base64_decode_tab[chr[1]] & 0xFF; + char3 = base64_decode_tab[chr[2]] & 0xFF; + char4 = base64_decode_tab[chr[3]] & 0xFF; + *(outptr++) = ((char1 << 2) & 0xFC) | + ((char2 >> 4) & 0x03); + *(outptr++) = ((char2 << 4) & 0xF0) | + ((char3 >> 2) & 0x0F); + *(outptr++) = ((char3 << 6) & 0xC0) | + (char4 & 0x3F); + rc = 3; + break; + } + return (rc); +} + +#define BUFSIZE 12 + +int +iscsi_base64_str_to_binary(char *hstr, int hstr_len, + uint8_t *binary, int binary_buf_len, int *out_len) +{ + char *iptr; + uint8_t tmp_out[BUFSIZE]; + int octets, endseen, numbase64chars; + unsigned char chr[4], curchr; + + /* + * base64 decode algorith, adapted from uudecode.c + * + * A valid base64 string is a multiple of 4 bytes in length + */ + if ((hstr_len % 4) != 0) + return (EINVAL); + + endseen = numbase64chars = 0; + *out_len = 0; + iptr = hstr; + + while (((curchr = *(iptr++)) != NULL) && + (((uintptr_t)iptr - (uintptr_t)hstr) <= hstr_len)) { + /* decode chars */ + if (curchr == '=') /* if end */ + endseen++; + + if (validbase64(curchr)) + chr[numbase64chars++] = curchr; + /* + * if we've gathered 4 base64 octets + * we need to decode and output them + */ + if (numbase64chars == 4) { + octets = outdec64(tmp_out, chr, 4); + numbase64chars = 0; + + if (*out_len + octets > binary_buf_len) + return (E2BIG); + + (void) memcpy(binary + *out_len, tmp_out, octets); + *out_len += octets; + } + + /* + * handle any remaining base64 octets at end + */ + if (endseen && numbase64chars > 0) { + octets = outdec64(tmp_out, chr, numbase64chars); + numbase64chars = 0; + if (*out_len + octets > binary_buf_len) + return (E2BIG); + + (void) memcpy(binary + *out_len, tmp_out, octets); + *out_len += octets; + } + } + + return (0); +} + + +static char base64_encode_tab[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789+/"; + +#define ENC(c) base64_encode_tab[(c) & 0x3f] + +#define BASE64_BUF_HAS_ROOM(bytes_needed) \ + ((optr + (bytes_needed)) <= base64_str_buf + base64_buf_len) + +int +iscsi_binary_to_base64_str(uint8_t *in_buf, int in_buf_len, + char *base64_str_buf, int base64_buf_len) +{ + uint8_t *iptr; + char *optr; + int in_bytes_remaining; + + /* base64 encode algorith, adapted from uuencode.c */ + iptr = in_buf; + optr = base64_str_buf; + + /* + * read must be a multiple of 3 bytes for + * this algorithm to work, and also must + * be small enough that read_size * (4/3) + * will always be 76 bytes or less, since + * base64 lines can be no longer than that + */ + while (iptr + 3 <= in_buf + in_buf_len) { + if (!BASE64_BUF_HAS_ROOM(4)) + return (E2BIG); + + *(optr++) = ENC(*iptr >> 2); + *(optr++) = ENC((*iptr << 4) & 060 | + (*(iptr + 1) >> 4) & 017); + *(optr++) = ENC((*(iptr + 1) << 2) + & 074 | (*(iptr + 2) >> 6) & 03); + *(optr++) = ENC(*(iptr + 2) & 077); + + iptr += 3; + } + + /* need output padding ? */ + in_bytes_remaining = ((uintptr_t)in_buf + in_buf_len) - (uintptr_t)iptr; + /* ASSERT(in_bytes_remaining < 3); */ + switch (in_bytes_remaining) { + case 0: + /* no-op - 24 bits of data encoded */ + if (!BASE64_BUF_HAS_ROOM(1)) + return (E2BIG); + *(optr++) = '\0'; + break; + case 1: + /* 8 bits encoded - pad with 2 '=' */ + if (!BASE64_BUF_HAS_ROOM(5)) + return (E2BIG); + *(optr++) = ENC((*iptr & 0xFC) >> 2); + *(optr++) = ENC((*iptr & 0x03) << 4); + *(optr++) = '='; + *(optr++) = '='; + *(optr++) = '\0'; + break; + case 2: + /* 16 bits encoded - pad with 1 '=' */ + if (!BASE64_BUF_HAS_ROOM(5)) + return (E2BIG); + *(optr++) = ENC((*iptr & 0xFC) >> 2); + *(optr++) = ENC(((*iptr & 0x03) << 4) | + ((*(iptr + 1) & 0xF0) >> 4)); + *(optr++) = ENC((*(iptr + 1) & 0x0F) << 2); + *(optr++) = '='; + *(optr++) = '\0'; + break; + default: + /* impossible */ + break; + } + + return (0); +} diff --git a/usr/src/common/iscsit/iscsit_common.c b/usr/src/common/iscsit/iscsit_common.c new file mode 100644 index 000000000000..642272fb64ba --- /dev/null +++ b/usr/src/common/iscsit/iscsit_common.c @@ -0,0 +1,1590 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include + +#if defined(_KERNEL) +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include + +void * +iscsit_zalloc(size_t size) +{ +#if defined(_KERNEL) + return (kmem_zalloc(size, KM_SLEEP)); +#else + return (calloc(1, size)); +#endif +} + +void +iscsit_free(void *buf, size_t size) /* ARGSUSED */ +{ +#if defined(_KERNEL) + kmem_free(buf, size); +#else + free(buf); +#endif +} + +/* + * default_port should be the port to be used, if not specified + * as part of the supplied string 'arg'. + */ + +#define NI_MAXHOST 1025 +#define NI_MAXSERV 32 + + +struct sockaddr_storage * +it_common_convert_sa(char *arg, struct sockaddr_storage *buf, + uint32_t default_port) +{ + /* Why does addrbuf need to be this big!??! XXX */ + char addrbuf[NI_MAXHOST + NI_MAXSERV + 1]; + char *addr_str; + char *port_str; +#ifndef _KERNEL + char *errchr; +#endif + long tmp_port = 0; + sa_family_t af; + + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + struct sockaddr_storage *sa = buf; + + if (!arg || !buf) { + return (NULL); + } + + bzero(buf, sizeof (struct sockaddr_storage)); + + /* don't modify the passed-in string */ + (void) strlcpy(addrbuf, arg, sizeof (addrbuf)); + + addr_str = addrbuf; + + if (*addr_str == '[') { + /* + * An IPv6 address must be inside square brackets + */ + port_str = strchr(addr_str, ']'); + if (!port_str) { + /* No closing bracket */ + return (NULL); + } + + /* strip off the square brackets so we can convert */ + addr_str++; + *port_str = '\0'; + port_str++; + + if (*port_str == ':') { + /* TCP port to follow */ + port_str++; + } else if (*port_str == '\0') { + /* No port specified */ + port_str = NULL; + } else { + /* malformed */ + return (NULL); + } + af = AF_INET6; + } else { + port_str = strchr(addr_str, ':'); + if (port_str) { + *port_str = '\0'; + port_str++; + } + af = AF_INET; + } + + if (port_str) { +#if defined(_KERNEL) + if (ddi_strtol(port_str, NULL, 10, &tmp_port) != 0) { + return (NULL); + } +#else + tmp_port = strtol(port_str, &errchr, 10); +#endif + if (tmp_port < 0 || tmp_port > 65535) { + return (NULL); + } + } else { + tmp_port = default_port; + } + + sa->ss_family = af; + + sin = (struct sockaddr_in *)sa; + if (af == AF_INET) { + if (inet_pton(af, addr_str, + (void *)&(sin->sin_addr.s_addr)) != 1) { + return (NULL); + } + /* + * intet_pton does not seem to convert to network + * order in kernel. This is a workaround until the + * inet_pton works or we have our own inet_pton function. + */ +#ifdef _KERNEL + sin->sin_addr.s_addr = ntohl((uint32_t)sin->sin_addr.s_addr); +#endif + sin->sin_port = htons(tmp_port); + } else { + sin6 = (struct sockaddr_in6 *)sa; + if (inet_pton(af, addr_str, + (void *)&(sin6->sin6_addr.s6_addr)) != 1) { + return (NULL); + } + sin6->sin6_port = htons(tmp_port); + } + + /* successful */ + return (sa); +} + + +/* Functions to convert iSCSI target structures to/from nvlists. */ + +#ifndef _KERNEL +int +it_config_to_nv(it_config_t *cfg, nvlist_t **nvl) +{ + int ret; + nvlist_t *nv; + nvlist_t *lnv = NULL; + + if (!nvl) { + return (EINVAL); + } + + *nvl = NULL; + + ret = nvlist_alloc(&nv, NV_UNIQUE_NAME_TYPE, 0); + if (ret != 0) { + return (ret); + } + + /* if there's no config, store an empty list */ + if (!cfg) { + *nvl = nv; + return (0); + } + + ret = nvlist_add_uint32(nv, "cfgVersion", cfg->config_version); + if (ret == 0) { + ret = it_tgtlist_to_nv(cfg->config_tgt_list, &lnv); + } + + if ((ret == 0) && (lnv != NULL)) { + ret = nvlist_add_nvlist(nv, "targetList", lnv); + nvlist_free(lnv); + lnv = NULL; + } + + if (ret == 0) { + ret = it_tpglist_to_nv(cfg->config_tpg_list, &lnv); + } + + if ((ret == 0) && (lnv != NULL)) { + ret = nvlist_add_nvlist(nv, "tpgList", lnv); + nvlist_free(lnv); + lnv = NULL; + } + + if (ret == 0) { + ret = it_inilist_to_nv(cfg->config_ini_list, &lnv); + } + + if ((ret == 0) && (lnv != NULL)) { + ret = nvlist_add_nvlist(nv, "iniList", lnv); + nvlist_free(lnv); + lnv = NULL; + } + + if (ret == 0) { + ret = nvlist_add_nvlist(nv, "globalProperties", + cfg->config_global_properties); + } + + if (ret == 0) { + *nvl = nv; + } else { + nvlist_free(nv); + } + + return (ret); +} +#endif /* !_KERNEL */ + +/* + * nvlist version of config is 3 list-of-list, + 1 proplist. arrays + * are interesting, but lists-of-lists are more useful when doing + * individual lookups when we later add support for it. Also, no + * need to store name in individual struct representation. + */ +int +it_nv_to_config(nvlist_t *nvl, it_config_t **cfg) +{ + int ret; + uint32_t intval; + nvlist_t *listval; + it_config_t *tmpcfg; + + if (!cfg) { + return (EINVAL); + } + + /* initialize output */ + *cfg = NULL; + + tmpcfg = iscsit_zalloc(sizeof (it_config_t)); + if (tmpcfg == NULL) { + return (ENOMEM); + } + + if (!nvl) { + /* nothing to decode, but return the empty cfg struct */ + ret = nvlist_alloc(&tmpcfg->config_global_properties, + NV_UNIQUE_NAME, 0); + if (ret != 0) { + iscsit_free(tmpcfg, sizeof (it_config_t)); + return (ret); + } + *cfg = tmpcfg; + return (0); + } + + ret = nvlist_lookup_uint32(nvl, "cfgVersion", &intval); + if (ret != 0) { + iscsit_free(tmpcfg, sizeof (it_config_t)); + return (ret); + } + + tmpcfg->config_version = intval; + + ret = nvlist_lookup_nvlist(nvl, "targetList", &listval); + if (ret == 0) { + /* decode list of it_tgt_t */ + ret = it_nv_to_tgtlist(listval, &(tmpcfg->config_tgt_count), + &(tmpcfg->config_tgt_list)); + } + + ret = nvlist_lookup_nvlist(nvl, "tpgList", &listval); + if (ret == 0) { + /* decode list of it_tpg_t */ + ret = it_nv_to_tpglist(listval, &(tmpcfg->config_tpg_count), + &(tmpcfg->config_tpg_list)); + } + + ret = nvlist_lookup_nvlist(nvl, "iniList", &listval); + if (ret == 0) { + /* decode list of initiators */ + ret = it_nv_to_inilist(listval, &(tmpcfg->config_ini_count), + &(tmpcfg->config_ini_list)); + } + + ret = nvlist_lookup_nvlist(nvl, "globalProperties", &listval); + if (ret == 0) { + /* + * don't depend on the original nvlist staying in-scope, + * duplicate the nvlist + */ + ret = nvlist_dup(listval, &(tmpcfg->config_global_properties), + 0); + } else if (ret == ENOENT) { + /* + * No global properties defined, make an empty list + */ + ret = nvlist_alloc(&tmpcfg->config_global_properties, + NV_UNIQUE_NAME, 0); + } + + if (ret == 0) { + char **isnsArray = NULL; + uint32_t numisns = 0; + + /* + * decode the list of iSNS server information to make + * references from the kernel simpler. + */ + if (tmpcfg->config_global_properties) { + ret = nvlist_lookup_string_array( + tmpcfg->config_global_properties, + PROP_ISNS_SERVER, + &isnsArray, &numisns); + if (ret == 0) { + ret = it_array_to_portallist(isnsArray, + numisns, ISNS_DEFAULT_SERVER_PORT, + &tmpcfg->config_isns_svr_list, + &tmpcfg->config_isns_svr_count); + } else if (ret == ENOENT) { + /* It's OK if we don't have any iSNS servers */ + ret = 0; + } + } + } + + if (ret == 0) { + *cfg = tmpcfg; + } else { + it_config_free_cmn(tmpcfg); + } + + return (ret); +} + +it_tgt_t * +it_tgt_lookup(it_config_t *cfg, char *tgt_name) +{ + it_tgt_t *cfg_tgt = NULL; + + for (cfg_tgt = cfg->config_tgt_list; + cfg_tgt != NULL; + cfg_tgt = cfg_tgt->tgt_next) { + if (strncmp(cfg_tgt->tgt_name, tgt_name, + MAX_ISCSI_NODENAMELEN) == 0) { + return (cfg_tgt); + } + } + + return (NULL); +} + +int +it_nv_to_tgtlist(nvlist_t *nvl, uint32_t *count, it_tgt_t **tgtlist) +{ + int ret = 0; + it_tgt_t *tgt; + it_tgt_t *prev = NULL; + nvpair_t *nvp = NULL; + nvlist_t *nvt; + char *name; + + if (!tgtlist || !count) { + return (EINVAL); + } + + *tgtlist = NULL; + *count = 0; + + if (!nvl) { + /* nothing to do */ + return (0); + } + + while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) { + name = nvpair_name(nvp); + + ret = nvpair_value_nvlist(nvp, &nvt); + if (ret != 0) { + /* invalid entry? */ + continue; + } + + ret = it_nv_to_tgt(nvt, name, &tgt); + if (ret != 0) { + break; + } + + (*count)++; + + if (*tgtlist == NULL) { + *tgtlist = tgt; + } else { + prev->tgt_next = tgt; + } + prev = tgt; + } + + if (ret != 0) { + it_tgt_free_cmn(*tgtlist); + *tgtlist = NULL; + } + + return (ret); +} + +int +it_tgtlist_to_nv(it_tgt_t *tgtlist, nvlist_t **nvl) +{ + int ret; + it_tgt_t *tgtp = tgtlist; + nvlist_t *pnv = NULL; + nvlist_t *tnv; + + if (!nvl) { + return (EINVAL); + } + + if (!tgtlist) { + /* nothing to do */ + return (0); + } + + /* create the target list if required */ + if (*nvl == NULL) { + ret = nvlist_alloc(&pnv, NV_UNIQUE_NAME, 0); + if (ret != 0) { + return (ret); + } + *nvl = pnv; + } + + while (tgtp) { + ret = it_tgt_to_nv(tgtp, &tnv); + + if (ret != 0) { + break; + } + + ret = nvlist_add_nvlist(*nvl, tgtp->tgt_name, tnv); + + if (ret != 0) { + break; + } + + nvlist_free(tnv); + + tgtp = tgtp->tgt_next; + } + + if (ret != 0) { + if (pnv) { + nvlist_free(pnv); + *nvl = NULL; + } + } + + return (ret); +} + +int +it_tgt_to_nv(it_tgt_t *tgt, nvlist_t **nvl) +{ + int ret; + nvlist_t *tnv = NULL; + + if (!nvl) { + return (EINVAL); + } + + if (!tgt) { + /* nothing to do */ + return (0); + } + + ret = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0); + if (ret != 0) { + return (ret); + } + + if (tgt->tgt_properties) { + ret = nvlist_add_nvlist(*nvl, "properties", + tgt->tgt_properties); + } + + if (ret == 0) { + ret = nvlist_add_uint64(*nvl, "generation", + tgt->tgt_generation); + } + + if (ret == 0) { + ret = it_tpgtlist_to_nv(tgt->tgt_tpgt_list, &tnv); + } + + if ((ret == 0) && tnv) { + ret = nvlist_add_nvlist(*nvl, "tpgtList", tnv); + nvlist_free(tnv); + } + + if (ret != 0) { + nvlist_free(*nvl); + *nvl = NULL; + } + + return (ret); +} + +int +it_nv_to_tgt(nvlist_t *nvl, char *name, it_tgt_t **tgt) +{ + int ret; + it_tgt_t *ttgt; + nvlist_t *listval; + uint32_t intval; + + if (!nvl || !tgt || !name) { + return (EINVAL); + } + + *tgt = NULL; + + ttgt = iscsit_zalloc(sizeof (it_tgt_t)); + if (!ttgt) { + return (ENOMEM); + } + + (void) strlcpy(ttgt->tgt_name, name, sizeof (ttgt->tgt_name)); + + ret = nvlist_lookup_nvlist(nvl, "properties", &listval); + if (ret == 0) { + /* duplicate list so it does not go out of context */ + ret = nvlist_dup(listval, &(ttgt->tgt_properties), 0); + } else if (ret == ENOENT) { + ret = 0; + } + + if (ret == 0) { + ret = nvlist_lookup_uint64(nvl, "generation", + &(ttgt->tgt_generation)); + } else if (ret == ENOENT) { + ret = 0; + } + + if (ret == 0) { + ret = nvlist_lookup_nvlist(nvl, "tpgtList", &listval); + } + + if (ret == 0) { + ret = it_nv_to_tpgtlist(listval, &intval, + &(ttgt->tgt_tpgt_list)); + ttgt->tgt_tpgt_count = intval; + } else if (ret == ENOENT) { + ret = 0; + } + + if (ret == 0) { + *tgt = ttgt; + } else { + it_tgt_free_cmn(ttgt); + } + + return (ret); +} + +int +it_tpgt_to_nv(it_tpgt_t *tpgt, nvlist_t **nvl) +{ + int ret; + + if (!nvl) { + return (EINVAL); + } + + if (!tpgt) { + /* nothing to do */ + return (0); + } + + ret = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0); + if (ret != 0) { + return (ret); + } + + ret = nvlist_add_uint16(*nvl, "tag", tpgt->tpgt_tag); + if (ret == 0) { + ret = nvlist_add_uint64(*nvl, "generation", + tpgt->tpgt_generation); + } + + if (ret != 0) { + nvlist_free(*nvl); + *nvl = NULL; + } + + return (ret); +} + +int +it_nv_to_tpgt(nvlist_t *nvl, char *name, it_tpgt_t **tpgt) +{ + int ret; + it_tpgt_t *ptr; + + if (!tpgt || !name) { + return (EINVAL); + } + + *tpgt = NULL; + + if (!nvl) { + return (0); + } + + ptr = iscsit_zalloc(sizeof (it_tpgt_t)); + if (!ptr) { + return (ENOMEM); + } + + (void) strlcpy(ptr->tpgt_tpg_name, name, sizeof (ptr->tpgt_tpg_name)); + + ret = nvlist_lookup_uint16(nvl, "tag", &(ptr->tpgt_tag)); + if (ret == 0) { + ret = nvlist_lookup_uint64(nvl, "generation", + &(ptr->tpgt_generation)); + } + + if (ret == 0) { + *tpgt = ptr; + } else { + iscsit_free(ptr, sizeof (it_tpgt_t)); + } + + return (ret); +} + +int +it_tpgtlist_to_nv(it_tpgt_t *tpgtlist, nvlist_t **nvl) +{ + int ret; + nvlist_t *pnv = NULL; + nvlist_t *tnv; + it_tpgt_t *ptr = tpgtlist; + + if (!nvl) { + return (EINVAL); + } + + if (!tpgtlist) { + /* nothing to do */ + return (0); + } + + /* create the target list if required */ + if (*nvl == NULL) { + ret = nvlist_alloc(&pnv, NV_UNIQUE_NAME, 0); + if (ret != 0) { + return (ret); + } + *nvl = pnv; + } + + while (ptr) { + ret = it_tpgt_to_nv(ptr, &tnv); + + if (ret != 0) { + break; + } + + ret = nvlist_add_nvlist(*nvl, ptr->tpgt_tpg_name, tnv); + + if (ret != 0) { + break; + } + + nvlist_free(tnv); + + ptr = ptr->tpgt_next; + } + + if (ret != 0) { + if (pnv) { + nvlist_free(pnv); + *nvl = NULL; + } + } + + return (ret); +} + +int +it_nv_to_tpgtlist(nvlist_t *nvl, uint32_t *count, it_tpgt_t **tpgtlist) +{ + int ret = 0; + it_tpgt_t *tpgt; + it_tpgt_t *prev = NULL; + nvpair_t *nvp = NULL; + nvlist_t *nvt; + char *name; + + if (!tpgtlist || !count) { + return (EINVAL); + } + + *tpgtlist = NULL; + *count = 0; + + if (!nvl) { + /* nothing to do */ + return (0); + } + + while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) { + name = nvpair_name(nvp); + + ret = nvpair_value_nvlist(nvp, &nvt); + if (ret != 0) { + /* invalid entry? */ + continue; + } + + ret = it_nv_to_tpgt(nvt, name, &tpgt); + if (ret != 0) { + break; + } + + (*count)++; + + if (*tpgtlist == NULL) { + *tpgtlist = tpgt; + } else { + prev->tpgt_next = tpgt; + } + + prev = tpgt; + } + + if (ret != 0) { + it_tpgt_free_cmn(*tpgtlist); + *tpgtlist = NULL; + } + + return (ret); +} + +#ifndef _KERNEL +int +it_tpg_to_nv(it_tpg_t *tpg, nvlist_t **nvl) +{ + int ret; + char **portalArray = NULL; + int i; + it_portal_t *ptr; + + if (!nvl) { + return (EINVAL); + } + + if (!tpg) { + /* nothing to do */ + return (0); + } + + ret = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0); + if (ret != 0) { + return (ret); + } + + ret = nvlist_add_uint64(*nvl, "generation", tpg->tpg_generation); + + if ((ret == 0) && tpg->tpg_portal_list) { + /* add the portals */ + portalArray = iscsit_zalloc(tpg->tpg_portal_count * + sizeof (it_portal_t)); + if (portalArray == NULL) { + nvlist_free(*nvl); + *nvl = NULL; + return (ENOMEM); + } + + i = 0; + ptr = tpg->tpg_portal_list; + + while (ptr && (i < tpg->tpg_portal_count)) { + ret = sockaddr_to_str(&(ptr->portal_addr), + &(portalArray[i])); + if (ret != 0) { + break; + } + ptr = ptr->next; + i++; + } + } + + if ((ret == 0) && portalArray) { + ret = nvlist_add_string_array(*nvl, "portalList", + portalArray, i); + } + + + if (portalArray) { + while (i > 0) { + if (portalArray[i]) { + iscsit_free(portalArray[i], + strlen(portalArray[i] + 1)); + } + i--; + } + iscsit_free(portalArray, + tpg->tpg_portal_count * sizeof (it_portal_t)); + } + + if (ret != 0) { + nvlist_free(*nvl); + *nvl = NULL; + } + + return (ret); +} +#endif /* !_KERNEL */ + +int +it_nv_to_tpg(nvlist_t *nvl, char *name, it_tpg_t **tpg) +{ + int ret; + it_tpg_t *ptpg; + char **portalArray = NULL; + uint32_t count = 0; + + if (!name || !tpg) { + return (EINVAL); + } + + *tpg = NULL; + + ptpg = iscsit_zalloc(sizeof (it_tpg_t)); + if (ptpg == NULL) { + return (ENOMEM); + } + + (void) strlcpy(ptpg->tpg_name, name, sizeof (ptpg->tpg_name)); + + ret = nvlist_lookup_uint64(nvl, "generation", + &(ptpg->tpg_generation)); + + if (ret == 0) { + ret = nvlist_lookup_string_array(nvl, "portalList", + &portalArray, &count); + } + + if (ret == 0) { + /* set the portals */ + ret = it_array_to_portallist(portalArray, count, + ISCSI_LISTEN_PORT, &ptpg->tpg_portal_list, + &ptpg->tpg_portal_count); + } else if (ret == ENOENT) { + ret = 0; + } + + if (ret == 0) { + *tpg = ptpg; + } else { + it_tpg_free_cmn(ptpg); + } + + return (ret); +} + + + + +#ifndef _KERNEL +int +it_tpglist_to_nv(it_tpg_t *tpglist, nvlist_t **nvl) +{ + int ret; + nvlist_t *pnv = NULL; + nvlist_t *tnv; + it_tpg_t *ptr = tpglist; + + if (!nvl) { + return (EINVAL); + } + + if (!tpglist) { + /* nothing to do */ + return (0); + } + + /* create the target portal group list if required */ + if (*nvl == NULL) { + ret = nvlist_alloc(&pnv, NV_UNIQUE_NAME, 0); + if (ret != 0) { + return (ret); + } + *nvl = pnv; + } + + while (ptr) { + ret = it_tpg_to_nv(ptr, &tnv); + + if (ret != 0) { + break; + } + + ret = nvlist_add_nvlist(*nvl, ptr->tpg_name, tnv); + + if (ret != 0) { + break; + } + + nvlist_free(tnv); + + ptr = ptr->tpg_next; + } + + if (ret != 0) { + if (pnv) { + nvlist_free(pnv); + *nvl = NULL; + } + } + + return (ret); +} +#endif /* !_KERNEL */ + +it_tpg_t * +it_tpg_lookup(it_config_t *cfg, char *tpg_name) +{ + it_tpg_t *cfg_tpg = NULL; + + for (cfg_tpg = cfg->config_tpg_list; + cfg_tpg != NULL; + cfg_tpg = cfg_tpg->tpg_next) { + if (strncmp(&cfg_tpg->tpg_name[0], tpg_name, + MAX_TPG_NAMELEN) == 0) { + return (cfg_tpg); + } + } + + return (NULL); +} + +int +it_sa_compare(struct sockaddr_storage *sa1, struct sockaddr_storage *sa2) +{ + struct sockaddr_in *sin1, *sin2; + struct sockaddr_in6 *sin6_1, *sin6_2; + + /* + * XXX - should we check here for IPv4 addrs mapped to v6? + * see also iscsit_is_v4_mapped in iscsit_login.c + */ + + if (sa1->ss_family != sa2->ss_family) { + return (1); + } + + /* + * sockaddr_in has padding which may not be initialized. + * be more specific in the comparison, and don't trust the + * caller has fully initialized the structure. + */ + if (sa1->ss_family == AF_INET) { + sin1 = (struct sockaddr_in *)sa1; + sin2 = (struct sockaddr_in *)sa2; + if ((bcmp(&sin1->sin_addr, &sin2->sin_addr, + sizeof (struct in_addr)) == 0) && + (sin1->sin_port == sin2->sin_port)) { + return (0); + } + } else if (sa1->ss_family == AF_INET6) { + sin6_1 = (struct sockaddr_in6 *)sa1; + sin6_2 = (struct sockaddr_in6 *)sa2; + if (bcmp(sin6_1, sin6_2, sizeof (struct sockaddr_in6)) == 0) { + return (0); + } + } + + return (1); +} + +it_portal_t * +it_portal_lookup(it_tpg_t *tpg, struct sockaddr_storage *sa) +{ + it_portal_t *cfg_portal; + + for (cfg_portal = tpg->tpg_portal_list; + cfg_portal != NULL; + cfg_portal = cfg_portal->next) { + if (it_sa_compare(sa, &cfg_portal->portal_addr) == 0) + return (cfg_portal); + } + + return (NULL); +} + +it_portal_t * +it_sns_svr_lookup(it_config_t *cfg, struct sockaddr_storage *sa) +{ + it_portal_t *cfg_portal; + + for (cfg_portal = cfg->config_isns_svr_list; + cfg_portal != NULL; + cfg_portal = cfg_portal->next) { + if (it_sa_compare(sa, &cfg_portal->portal_addr) == 0) + return (cfg_portal); + } + + return (NULL); +} + +int +it_nv_to_tpglist(nvlist_t *nvl, uint32_t *count, it_tpg_t **tpglist) +{ + int ret = 0; + it_tpg_t *tpg; + it_tpg_t *prev = NULL; + nvpair_t *nvp = NULL; + nvlist_t *nvt; + char *name; + + if (!tpglist || !count) { + return (EINVAL); + } + + *tpglist = NULL; + *count = 0; + + if (!nvl) { + /* nothing to do */ + return (0); + } + + while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) { + name = nvpair_name(nvp); + + ret = nvpair_value_nvlist(nvp, &nvt); + if (ret != 0) { + /* invalid entry? */ + continue; + } + + ret = it_nv_to_tpg(nvt, name, &tpg); + if (ret != 0) { + break; + } + + (*count)++; + + if (*tpglist == NULL) { + *tpglist = tpg; + } else { + prev->tpg_next = tpg; + } + prev = tpg; + } + + if (ret != 0) { + it_tpg_free_cmn(*tpglist); + *tpglist = NULL; + } + + return (ret); +} + +int +it_ini_to_nv(it_ini_t *ini, nvlist_t **nvl) +{ + int ret; + + if (!nvl) { + return (EINVAL); + } + + if (!ini) { + return (0); + } + + ret = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0); + if (ret != 0) { + return (ret); + } + + if (ini->ini_properties) { + ret = nvlist_add_nvlist(*nvl, "properties", + ini->ini_properties); + } + + if (ret == 0) { + ret = nvlist_add_uint64(*nvl, "generation", + ini->ini_generation); + } else if (ret == ENOENT) { + ret = 0; + } + + if (ret != 0) { + nvlist_free(*nvl); + *nvl = NULL; + } + + return (ret); +} + +int +it_nv_to_ini(nvlist_t *nvl, char *name, it_ini_t **ini) +{ + int ret; + it_ini_t *inip; + nvlist_t *listval; + + if (!name || !ini) { + return (EINVAL); + } + + *ini = NULL; + + if (!nvl) { + return (0); + } + + inip = iscsit_zalloc(sizeof (it_ini_t)); + if (!inip) { + return (ENOMEM); + } + + (void) strlcpy(inip->ini_name, name, sizeof (inip->ini_name)); + + ret = nvlist_lookup_nvlist(nvl, "properties", &listval); + if (ret == 0) { + ret = nvlist_dup(listval, &(inip->ini_properties), 0); + } else if (ret == ENOENT) { + ret = 0; + } + + if (ret == 0) { + ret = nvlist_lookup_uint64(nvl, "generation", + &(inip->ini_generation)); + } + + if (ret == 0) { + *ini = inip; + } else { + it_ini_free_cmn(inip); + } + + return (ret); +} + +int +it_inilist_to_nv(it_ini_t *inilist, nvlist_t **nvl) +{ + int ret; + nvlist_t *pnv = NULL; + nvlist_t *tnv; + it_ini_t *ptr = inilist; + + if (!nvl) { + return (EINVAL); + } + + if (!inilist) { + return (0); + } + + /* create the target list if required */ + if (*nvl == NULL) { + ret = nvlist_alloc(&pnv, NV_UNIQUE_NAME, 0); + if (ret != 0) { + return (ret); + } + *nvl = pnv; + } + + while (ptr) { + ret = it_ini_to_nv(ptr, &tnv); + + if (ret != 0) { + break; + } + + ret = nvlist_add_nvlist(*nvl, ptr->ini_name, tnv); + + if (ret != 0) { + break; + } + + nvlist_free(tnv); + + ptr = ptr->ini_next; + } + + if (ret != 0) { + if (pnv) { + nvlist_free(pnv); + *nvl = NULL; + } + } + + return (ret); +} + +int +it_nv_to_inilist(nvlist_t *nvl, uint32_t *count, it_ini_t **inilist) +{ + int ret = 0; + it_ini_t *inip; + it_ini_t *prev = NULL; + nvpair_t *nvp = NULL; + nvlist_t *nvt; + char *name; + + if (!inilist || !count) { + return (EINVAL); + } + + *inilist = NULL; + *count = 0; + + if (!nvl) { + /* nothing to do */ + return (0); + } + + while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) { + name = nvpair_name(nvp); + + ret = nvpair_value_nvlist(nvp, &nvt); + if (ret != 0) { + /* invalid entry? */ + continue; + } + + ret = it_nv_to_ini(nvt, name, &inip); + if (ret != 0) { + break; + } + + (*count)++; + + if (*inilist == NULL) { + *inilist = inip; + } else { + prev->ini_next = inip; + } + prev = inip; + } + + if (ret != 0) { + it_ini_free_cmn(*inilist); + *inilist = NULL; + } + + return (ret); +} + +/* + * Convert a sockaddr to the string representation, suitable for + * storing in an nvlist or printing out in a list. + */ +#ifndef _KERNEL +int +sockaddr_to_str(struct sockaddr_storage *sa, char **addr) +{ + int ret; + char buf[INET6_ADDRSTRLEN + 7]; /* addr : port */ + char pbuf[7]; + const char *bufp; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + uint16_t port; + + if (!sa || !addr) { + return (EINVAL); + } + + buf[0] = '\0'; + + if (sa->ss_family == AF_INET) { + sin = (struct sockaddr_in *)sa; + bufp = inet_ntop(AF_INET, + (const void *)&(sin->sin_addr.s_addr), + buf, sizeof (buf)); + if (bufp == NULL) { + ret = errno; + return (ret); + } + port = ntohs(sin->sin_port); + } else if (sa->ss_family == AF_INET6) { + (void) strlcat(buf, "[", sizeof (buf)); + sin6 = (struct sockaddr_in6 *)sa; + bufp = inet_ntop(AF_INET6, + (const void *)&sin6->sin6_addr.s6_addr, + &buf[1], (sizeof (buf) - 1)); + if (bufp == NULL) { + ret = errno; + return (ret); + } + (void) strlcat(buf, "]", sizeof (buf)); + port = ntohs(sin6->sin6_port); + } else { + return (EINVAL); + } + + + (void) snprintf(pbuf, sizeof (pbuf), ":%u", port); + (void) strlcat(buf, pbuf, sizeof (buf)); + + *addr = strdup(buf); + if (*addr == NULL) { + return (ENOMEM); + } + + return (0); +} +#endif /* !_KERNEL */ + +int +it_array_to_portallist(char **arr, uint32_t count, uint32_t default_port, + it_portal_t **portallist, uint32_t *list_count) +{ + int ret = 0; + int i; + it_portal_t *portal; + it_portal_t *prev = NULL; + it_portal_t *tmp; + + if (!arr || !portallist || !list_count) { + return (EINVAL); + } + + *list_count = 0; + *portallist = NULL; + + for (i = 0; i < count; i++) { + if (!arr[i]) { + /* should never happen */ + continue; + } + portal = iscsit_zalloc(sizeof (it_portal_t)); + if (!portal) { + ret = ENOMEM; + break; + } + if (it_common_convert_sa(arr[i], + &(portal->portal_addr), default_port) == NULL) { + iscsit_free(portal, sizeof (it_portal_t)); + ret = EINVAL; + break; + } + + /* make sure no duplicates */ + tmp = *portallist; + while (tmp) { + if (it_sa_compare(&(tmp->portal_addr), + &(portal->portal_addr)) == 0) { + iscsit_free(portal, sizeof (it_portal_t)); + portal = NULL; + break; + } + tmp = tmp->next; + } + + if (!portal) { + continue; + } + + /* + * The first time through the loop, *portallist == NULL + * because we assigned it to NULL above. Subsequently + * prev will have been set. Therefor it's OK to put + * lint override before prev->next assignment. + */ + if (*portallist == NULL) { + *portallist = portal; + } else { + prev->next = portal; + } + + prev = portal; + (*list_count)++; + } + + return (ret); +} + +/* + * Function: it_config_free_cmn() + * + * Free any resources associated with the it_config_t structure. + * + * Parameters: + * cfg A C representation of the current iSCSI configuration + */ +void +it_config_free_cmn(it_config_t *cfg) +{ + if (!cfg) { + return; + } + + if (cfg->config_tgt_list) { + it_tgt_free_cmn(cfg->config_tgt_list); + } + + if (cfg->config_tpg_list) { + it_tpg_free_cmn(cfg->config_tpg_list); + } + + if (cfg->config_ini_list) { + it_ini_free_cmn(cfg->config_ini_list); + } + + if (cfg->config_global_properties) { + nvlist_free(cfg->config_global_properties); + } + + if (cfg->config_isns_svr_list) { + it_portal_t *pp = cfg->config_isns_svr_list; + it_portal_t *pp_next; + + while (pp) { + pp_next = pp->next; + iscsit_free(pp, sizeof (it_portal_t)); + pp = pp_next; + } + } + + iscsit_free(cfg, sizeof (it_config_t)); +} + +/* + * Function: it_tgt_free_cmn() + * + * Frees an it_tgt_t structure. If tgt_next is not NULL, frees + * all structures in the list. + */ +void +it_tgt_free_cmn(it_tgt_t *tgt) +{ + it_tgt_t *tgtp = tgt; + it_tgt_t *next; + + if (!tgt) { + return; + } + + while (tgtp) { + next = tgtp->tgt_next; + + if (tgtp->tgt_tpgt_list) { + it_tpgt_free_cmn(tgtp->tgt_tpgt_list); + } + + if (tgtp->tgt_properties) { + nvlist_free(tgtp->tgt_properties); + } + + iscsit_free(tgtp, sizeof (it_tgt_t)); + + tgtp = next; + } +} + +/* + * Function: it_tpgt_free_cmn() + * + * Deallocates resources of an it_tpgt_t structure. If tpgt->next + * is not NULL, frees all members of the list. + */ +void +it_tpgt_free_cmn(it_tpgt_t *tpgt) +{ + it_tpgt_t *tpgtp = tpgt; + it_tpgt_t *next; + + if (!tpgt) { + return; + } + + while (tpgtp) { + next = tpgtp->tpgt_next; + + iscsit_free(tpgtp, sizeof (it_tpgt_t)); + + tpgtp = next; + } +} + +/* + * Function: it_tpg_free_cmn() + * + * Deallocates resources associated with an it_tpg_t structure. + * If tpg->next is not NULL, frees all members of the list. + */ +void +it_tpg_free_cmn(it_tpg_t *tpg) +{ + it_tpg_t *tpgp = tpg; + it_tpg_t *next; + it_portal_t *portalp; + it_portal_t *pnext; + + while (tpgp) { + next = tpgp->tpg_next; + + portalp = tpgp->tpg_portal_list; + + while (portalp) { + pnext = portalp->next; + iscsit_free(portalp, sizeof (it_portal_t)); + portalp = pnext; + } + + iscsit_free(tpgp, sizeof (it_tpg_t)); + + tpgp = next; + } +} + +/* + * Function: it_ini_free_cmn() + * + * Deallocates resources of an it_ini_t structure. If ini->next is + * not NULL, frees all members of the list. + */ +void +it_ini_free_cmn(it_ini_t *ini) +{ + it_ini_t *inip = ini; + it_ini_t *next; + + if (!ini) { + return; + } + + while (inip) { + next = inip->ini_next; + + if (inip->ini_properties) { + nvlist_free(inip->ini_properties); + } + + iscsit_free(inip, sizeof (it_ini_t)); + + inip = next; + } +} diff --git a/usr/src/lib/Makefile b/usr/src/lib/Makefile index 5b4d3f0d577e..94e5f1d7f357 100644 --- a/usr/src/lib/Makefile +++ b/usr/src/lib/Makefile @@ -113,6 +113,7 @@ SUBDIRS += \ libinetcfg \ libinetutil \ libipmp \ + libiscsit \ libiscsitgt \ libkmf \ libkstat \ @@ -388,6 +389,7 @@ HDRSUBDIRS= \ libipmi \ libipmp \ libipp \ + libiscsit \ libiscsitgt \ libkstat \ libkvm \ @@ -545,6 +547,7 @@ libfstyp: libnvpair libelfsign: libcryptoutil libkmf libidmap: libnsl libinetcfg: libnsl libsocket libdlpi +libiscsit: libc libnvpair libstmf libuuid libnsl libkmf: libcryptoutil pkcs11 openssl libnsl: libmd5 libscf libmapid: libresolv diff --git a/usr/src/lib/libiscsit/Makefile b/usr/src/lib/libiscsit/Makefile new file mode 100644 index 000000000000..e4dc29754355 --- /dev/null +++ b/usr/src/lib/libiscsit/Makefile @@ -0,0 +1,56 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.lib + +SUBDIRS = $(MACH) +$(BUILD64)SUBDIRS += $(MACH64) + +all := TARGET= all +clean := TARGET= clean +clobber := TARGET= clobber +install := TARGET= install +lint := TARGET= lint + + +HDRS= libiscsit.h +HDRDIR= common +ROOTHDRS= $(ROOTHDRDIR)/libiscsit.h + +.KEEP_STATE: + +all clean clobber install lint: $(SUBDIRS) + +$(ROOTHDRDIR)/%: common/% + $(INS.file) +install_h: $(ROOTHDRS) + +check: $(CHECKHDRS) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + diff --git a/usr/src/lib/libiscsit/Makefile.com b/usr/src/lib/libiscsit/Makefile.com new file mode 100644 index 000000000000..7e0a23201c86 --- /dev/null +++ b/usr/src/lib/libiscsit/Makefile.com @@ -0,0 +1,66 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +LIBRARY= libiscsit.a +VERS= .1 + +ISCSIT_OBJS_SHARED= iscsit_common.o +ISCSI_OBJS_SHARED= base64.o +OBJS_COMMON= libiscsit.o +OBJECTS= $(OBJS_COMMON) $(ISCSIT_OBJS_SHARED) $(ISCSI_OBJS_SHARED) + +include ../../Makefile.lib + +LIBS= $(DYNLIB) $(LINTLIB) + +SRCDIR = ../common + +INCS += -I$(SRCDIR) + +C99MODE= -xc99=%all +C99LMODE= -Xc99=%all +LDLIBS += -lc -lnvpair -lstmf -luuid -lnsl +CPPFLAGS += $(INCS) -D_REENTRANT + +SRCS= $(OBJS_COMMON:%.o=$(SRCDIR)/%.c) \ + $(ISCSIT_OBJS_SHARED:%.o=$(SRC)/common/iscsit/%.c) \ + $(ISCSI_OBJS_SHARED:%.o=$(SRC)/common/iscsi/%.c) +$(LINTLIB) := SRCS= $(SRCDIR)/$(LINTSRC) + +.KEEP_STATE: + +all: $(LIBS) + +lint: lintcheck + +pics/%.o: ../../../common/iscsit/%.c + $(COMPILE.c) -o $@ $< + $(POST_PROCESS_O) + +pics/%.o: ../../../common/iscsi/%.c + $(COMPILE.c) -o $@ $< + $(POST_PROCESS_O) + +include ../../Makefile.targ diff --git a/usr/src/lib/libiscsit/amd64/Makefile b/usr/src/lib/libiscsit/amd64/Makefile new file mode 100644 index 000000000000..821cbc8a8206 --- /dev/null +++ b/usr/src/lib/libiscsit/amd64/Makefile @@ -0,0 +1,32 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +include ../Makefile.com +include ../../Makefile.lib.64 + +all: $(LIBS) + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT) diff --git a/usr/src/lib/libiscsit/common/libiscsit.c b/usr/src/lib/libiscsit/common/libiscsit.c new file mode 100644 index 000000000000..a9b2edeae46b --- /dev/null +++ b/usr/src/lib/libiscsit/common/libiscsit.c @@ -0,0 +1,1889 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* From iscsitgtd */ +#define TARGET_NAME_VERS 2 + +/* this should be defined someplace central... */ +#define ISCSI_NAME_LEN_MAX 223 + +/* max length of a base64 encoded secret */ +#define MAX_BASE64_LEN 341 + +/* Default RADIUS server port */ +#define DEFAULT_RADIUS_PORT 1812 + +/* + * The kernel reserves target portal group tag value 1 as the default. + */ +#define ISCSIT_DEFAULT_TPGT 1 +#define MAXTAG 0xffff + +/* helper for property list validation */ +#define PROPERR(lst, key, value) { \ + if (lst) { \ + (void) nvlist_add_string(lst, key, value); \ + } \ +} + +/* helper function declarations */ +static int +it_iqn_generate(char *iqn_buf, int iqn_buf_len, char *opt_iqn_suffix); + +static int +it_val_pass(char *name, char *val, nvlist_t *e); + +/* consider making validate funcs public */ +static int +it_validate_configprops(nvlist_t *nvl, nvlist_t *errs); + +static int +it_validate_tgtprops(nvlist_t *nvl, nvlist_t *errs); + +static int +it_validate_iniprops(nvlist_t *nvl, nvlist_t *errs); + +/* + * Function: it_config_load() + * + * Allocate and create an it_config_t structure representing the + * current iSCSI configuration. This structure is compiled using + * the 'provider' data returned by stmfGetProviderData(). If there + * is no provider data associated with iscsit, the it_config_t + * structure will be set to a default configuration. + * + * Parameters: + * cfg A C representation of the current iSCSI configuration + * + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid parameter + */ +int +it_config_load(it_config_t **cfg) +{ + int ret = 0; + nvlist_t *cfg_nv = NULL; + it_config_t *newcfg = NULL; + uint64_t stmf_token = 0; + + if (!cfg) { + return (EINVAL); + } + + *cfg = NULL; + + ret = stmfGetProviderDataProt(ISCSIT_MODNAME, &cfg_nv, + STMF_PORT_PROVIDER_TYPE, &stmf_token); + + if ((ret == STMF_STATUS_SUCCESS) || + (ret == STMF_ERROR_NOT_FOUND)) { + /* + * If not initialized yet, return empty it_config_t + * Else, convert nvlist to struct + */ + ret = it_nv_to_config(cfg_nv, &newcfg); + } + + if (ret == 0) { + newcfg->stmf_token = stmf_token; + *cfg = newcfg; + } + + return (ret); +} + +/* + * Function: it_config_commit() + * + * Informs the iscsit service that the configuration has changed and + * commits the new configuration to persistent store by calling + * stmfSetProviderData. This function can be called multiple times + * during a configuration sequence if necessary. + * + * Parameters: + * cfg A C representation of the current iSCSI configuration + * + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid it_config_t structure + * TBD ioctl() failed + * TBD could not save config to STMF + */ +int +it_config_commit(it_config_t *cfg) +{ + int ret; + nvlist_t *cfgnv = NULL; + char *packednv = NULL; + int iscsit_fd = -1; + size_t pnv_size; + iscsit_ioc_set_config_t iop; + it_tgt_t *tgtp; + + if (!cfg) { + return (EINVAL); + } + + iscsit_fd = open(ISCSIT_NODE, O_RDWR|O_EXCL); + if (iscsit_fd == -1) { + ret = errno; + return (ret); + } + + ret = it_config_to_nv(cfg, &cfgnv); + if (ret == 0) { + ret = nvlist_size(cfgnv, &pnv_size, NV_ENCODE_NATIVE); + } + + if (ret == 0) { + packednv = malloc(pnv_size); + if (!packednv) { + ret = ENOMEM; + } else { + ret = nvlist_pack(cfgnv, &packednv, &pnv_size, + NV_ENCODE_NATIVE, 0); + } + } + + /* + * Send the changes to the kernel first, for now. Kernel + * will be the final sanity check before config is saved + * persistently. + * + * XXX - this leaves open the simultaneous-change hole + * that STMF was trying to solve, but is a better sanity + * check. Final decision on save order/config generation + * number TBD. + */ + if (ret == 0) { + iop.set_cfg_vers = ISCSIT_API_VERS0; + iop.set_cfg_pnvlist = packednv; + iop.set_cfg_pnvlist_len = pnv_size; + if ((ioctl(iscsit_fd, ISCSIT_IOC_SET_CONFIG, &iop)) != 0) { + ret = errno; + } + } + + /* + * Before saving the config persistently, remove any + * PROP_OLD_TARGET_NAME entries. This is only interesting to + * the active service. + */ + if (ret == 0) { + tgtp = cfg->config_tgt_list; + for (; tgtp != NULL; tgtp = tgtp->tgt_next) { + if (!tgtp->tgt_properties) { + continue; + } + if (nvlist_exists(tgtp->tgt_properties, + PROP_OLD_TARGET_NAME)) { + (void) nvlist_remove_all(tgtp->tgt_properties, + PROP_OLD_TARGET_NAME); + } + } + } + + /* + * stmfGetProviderDataProt() checks to ensure + * that the config data hasn't changed since we fetched it. + * + * The kernel now has a version we need to save persistently. + * CLI will 'do the right thing' and warn the user if it + * gets STMF_ERROR_PROV_DATA_STALE. We'll try once to revert + * the kernel to the persistently saved data, but ultimately, + * it's up to the administrator to validate things are as they + * want them to be. + */ + if (ret == 0) { + ret = stmfSetProviderDataProt(ISCSIT_MODNAME, cfgnv, + STMF_PORT_PROVIDER_TYPE, &(cfg->stmf_token)); + + if (ret == STMF_STATUS_SUCCESS) { + ret = 0; + } else if (ret == STMF_ERROR_NOMEM) { + ret = ENOMEM; + } else if (ret == STMF_ERROR_PROV_DATA_STALE) { + int st; + it_config_t *rcfg = NULL; + + st = it_config_load(&rcfg); + if (st == 0) { + (void) it_config_commit(rcfg); + it_config_free(rcfg); + } + } + } + + (void) close(iscsit_fd); + + if (packednv) { + free(packednv); + } + + if (cfgnv) { + nvlist_free(cfgnv); + } + + return (ret); +} + +/* + * Function: it_config_setprop() + * + * Validate the provided property list and set the global properties + * for iSCSI Target. If errlist is not NULL, returns detailed + * errors for each property that failed. The format for errorlist + * is key = property, value = error string. + * + * Parameters: + * + * cfg The current iSCSI configuration obtained from + * it_config_load() + * proplist nvlist_t containing properties for this target. + * errlist (optional) nvlist_t of errors encountered when + * validating the properties. + * + * Return Values: + * 0 Success + * EINVAL Invalid property + * + */ +int +it_config_setprop(it_config_t *cfg, nvlist_t *proplist, nvlist_t **errlist) +{ + int ret; + it_portal_t *isns = NULL; + it_portal_t *pnext = NULL; + it_portal_t *newisnslist = NULL; + char **arr; + uint32_t count; + uint32_t newcount; + nvlist_t *cprops = NULL; + char *val = NULL; + + if (!cfg || !proplist) { + return (EINVAL); + } + + if (errlist) { + (void) nvlist_alloc(errlist, 0, 0); + } + + /* + * copy the existing properties, merge, then validate + * the merged properties before committing them. + */ + if (cfg->config_global_properties) { + ret = nvlist_dup(cfg->config_global_properties, &cprops, 0); + } else { + ret = nvlist_alloc(&cprops, NV_UNIQUE_NAME, 0); + } + + /* base64 encode the radius secret, if it's changed */ + val = NULL; + (void) nvlist_lookup_string(proplist, PROP_RADIUS_SECRET, &val); + if (val) { + char bsecret[MAX_BASE64_LEN]; + + ret = it_val_pass(PROP_RADIUS_SECRET, val, *errlist); + + if (ret == 0) { + (void) memset(bsecret, 0, MAX_BASE64_LEN); + + ret = iscsi_binary_to_base64_str((uint8_t *)val, + strlen(val), bsecret, MAX_BASE64_LEN); + + if (ret == 0) { + /* replace the value in the nvlist */ + ret = nvlist_add_string(proplist, + PROP_RADIUS_SECRET, bsecret); + } + } + } + + if (ret == 0) { + ret = nvlist_merge(cprops, proplist, 0); + } + + /* see if we need to remove the radius server setting */ + val = NULL; + (void) nvlist_lookup_string(cprops, PROP_RADIUS_SERVER, &val); + if (val && (strcasecmp(val, "none") == 0)) { + (void) nvlist_remove_all(cprops, PROP_RADIUS_SERVER); + } + + /* and/or remove the alias */ + val = NULL; + (void) nvlist_lookup_string(cprops, PROP_ALIAS, &val); + if (val && (strcasecmp(val, "none") == 0)) { + (void) nvlist_remove_all(cprops, PROP_ALIAS); + } + + if (ret == 0) { + ret = it_validate_configprops(cprops, *errlist); + } + + if (ret != 0) { + if (cprops) { + nvlist_free(cprops); + } + return (ret); + } + + /* + * Update iSNS server list, if exists in provided property list. + */ + ret = nvlist_lookup_string_array(proplist, PROP_ISNS_SERVER, + &arr, &count); + + if (ret == 0) { + /* special case: if "none", remove all defined */ + if (strcasecmp(arr[0], "none") != 0) { + ret = it_array_to_portallist(arr, count, + ISNS_DEFAULT_SERVER_PORT, &newisnslist, &newcount); + } else { + newisnslist = NULL; + newcount = 0; + (void) nvlist_remove_all(cprops, PROP_ISNS_SERVER); + } + + if (ret == 0) { + isns = cfg->config_isns_svr_list; + while (isns) { + pnext = isns->next; + free(isns); + isns = pnext; + } + + cfg->config_isns_svr_list = newisnslist; + cfg->config_isns_svr_count = newcount; + + /* + * Replace the array in the nvlist to ensure + * duplicates are properly removed & port numbers + * are added. + */ + if (newcount > 0) { + int i = 0; + char **newarray; + + newarray = malloc(sizeof (char *) * newcount); + if (newarray == NULL) { + ret = ENOMEM; + } else { + for (isns = newisnslist; isns != NULL; + isns = isns->next) { + (void) sockaddr_to_str( + &(isns->portal_addr), + &(newarray[i++])); + } + (void) nvlist_add_string_array(cprops, + PROP_ISNS_SERVER, newarray, + newcount); + + for (i = 0; i < newcount; i++) { + if (newarray[i]) { + free(newarray[i]); + } + } + free(newarray); + } + } + } + } else if (ret == ENOENT) { + /* not an error */ + ret = 0; + } + + if (ret == 0) { + /* replace the global properties list */ + nvlist_free(cfg->config_global_properties); + cfg->config_global_properties = cprops; + } else { + if (cprops) { + nvlist_free(cprops); + } + } + + return (ret); +} + +/* + * Function: it_config_free() + * + * Free any resources associated with the it_config_t structure. + * + * Parameters: + * cfg A C representation of the current iSCSI configuration + */ +void +it_config_free(it_config_t *cfg) +{ + it_config_free_cmn(cfg); +} + +/* + * Function: it_tgt_create() + * + * Allocate and create an it_tgt_t structure representing a new iSCSI + * target node. If tgt_name is NULL, then a unique target node name will + * be generated automatically. Otherwise, the value of tgt_name will be + * used as the target node name. The new it_tgt_t structure is added to + * the target list (cfg_tgt_list) in the configuration structure, and the + * new target will not be instantiated until the modified configuration + * is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tgt Pointer to an iSCSI target structure + * tgt_name The target node name for the target to be created. + * The name must be in either IQN or EUI format. If + * this value is NULL, a node name will be generated + * automatically in IQN format. + * + * Return Values: + * 0 Success + * ENOMEM Could not allocated resources + * EINVAL Invalid parameter + * EFAULT Invalid iSCSI name specified + */ +int +it_tgt_create(it_config_t *cfg, it_tgt_t **tgt, char *tgt_name) +{ + int ret = 0; + it_tgt_t *ptr; + it_tgt_t *cfgtgt; + char *namep = tgt_name; + char buf[ISCSI_NAME_LEN_MAX + 1]; + + if (!cfg || !tgt) { + return (EINVAL); + } + + if (!namep) { + /* generate a name */ + + ret = it_iqn_generate(buf, sizeof (buf), NULL); + if (ret != 0) { + return (ret); + } + namep = buf; + } else { + /* validate the passed-in name */ + if (!validate_iscsi_name(namep)) { + return (EFAULT); + } + } + + /* make sure this name isn't already on the list */ + cfgtgt = cfg->config_tgt_list; + while (cfgtgt != NULL) { + if (strcmp(namep, cfgtgt->tgt_name) == 0) { + return (EEXIST); + } + cfgtgt = cfgtgt->tgt_next; + } + + ptr = calloc(1, sizeof (it_tgt_t)); + if (ptr == NULL) { + return (ENOMEM); + } + + (void) strlcpy(ptr->tgt_name, namep, sizeof (ptr->tgt_name)); + ptr->tgt_generation = 1; + ptr->tgt_next = cfg->config_tgt_list; + cfg->config_tgt_list = ptr; + cfg->config_tgt_count++; + + *tgt = ptr; + + return (0); +} + +/* + * Function: it_tgt_setprop() + * + * Validate the provided property list and set the properties for + * the specified target. If errlist is not NULL, returns detailed + * errors for each property that failed. The format for errorlist + * is key = property, value = error string. + * + * Parameters: + * + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tgt Pointer to an iSCSI target structure + * proplist nvlist_t containing properties for this target. + * errlist (optional) nvlist_t of errors encountered when + * validating the properties. + * + * Return Values: + * 0 Success + * EINVAL Invalid property + * + */ +int +it_tgt_setprop(it_config_t *cfg, it_tgt_t *tgt, nvlist_t *proplist, + nvlist_t **errlist) +{ + int ret; + nvlist_t *tprops = NULL; + char *val = NULL; + + if (!cfg || !tgt || !proplist) { + return (EINVAL); + } + + if (errlist) { + (void) nvlist_alloc(errlist, 0, 0); + } + + /* + * copy the existing properties, merge, then validate + * the merged properties before committing them. + */ + if (tgt->tgt_properties) { + ret = nvlist_dup(tgt->tgt_properties, &tprops, 0); + } else { + ret = nvlist_alloc(&tprops, NV_UNIQUE_NAME, 0); + } + + if (ret == 0) { + ret = nvlist_merge(tprops, proplist, 0); + } + + /* unset chap username or alias if requested */ + val = NULL; + (void) nvlist_lookup_string(proplist, PROP_TARGET_CHAP_USER, &val); + if (val && (strcasecmp(val, "none") == 0)) { + (void) nvlist_remove_all(tprops, PROP_TARGET_CHAP_USER); + } + + val = NULL; + (void) nvlist_lookup_string(proplist, PROP_ALIAS, &val); + if (val && (strcasecmp(val, "none") == 0)) { + (void) nvlist_remove_all(tprops, PROP_ALIAS); + } + + /* base64 encode the CHAP secret, if it's changed */ + val = NULL; + (void) nvlist_lookup_string(proplist, PROP_TARGET_CHAP_SECRET, &val); + if (val) { + char bsecret[MAX_BASE64_LEN]; + + ret = it_val_pass(PROP_TARGET_CHAP_SECRET, val, *errlist); + + if (ret == 0) { + (void) memset(bsecret, 0, MAX_BASE64_LEN); + + ret = iscsi_binary_to_base64_str((uint8_t *)val, + strlen(val), bsecret, MAX_BASE64_LEN); + + if (ret == 0) { + /* replace the value in the nvlist */ + ret = nvlist_add_string(tprops, + PROP_TARGET_CHAP_SECRET, bsecret); + } + } + } + + if (ret == 0) { + ret = it_validate_tgtprops(tprops, *errlist); + } + + if (ret != 0) { + if (tprops) { + nvlist_free(tprops); + } + return (ret); + } + + if (tgt->tgt_properties) { + nvlist_free(tgt->tgt_properties); + } + tgt->tgt_properties = tprops; + + return (0); +} + + +/* + * Function: it_tgt_delete() + * + * Delete target represented by 'tgt', where 'tgt' is an existing + * it_tgt_structure within the configuration 'cfg'. The target removal + * will not take effect until the modified configuration is committed + * by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tgt Pointer to an iSCSI target structure + * + * force Set the target to offline before removing it from + * the config. If not specified, the operation will + * fail if the target is determined to be online. + * Return Values: + * 0 Success + * EBUSY Target is online + */ +int +it_tgt_delete(it_config_t *cfg, it_tgt_t *tgt, boolean_t force) +{ + int ret; + it_tgt_t *ptgt; + it_tgt_t *prev = NULL; + stmfDevid devid; + stmfTargetProperties props; + + if (!cfg || !tgt) { + return (0); + } + + ptgt = cfg->config_tgt_list; + while (ptgt != NULL) { + if (strcmp(tgt->tgt_name, ptgt->tgt_name) == 0) { + break; + } + prev = ptgt; + ptgt = ptgt->tgt_next; + } + + if (!ptgt) { + return (0); + } + + /* + * check to see if this target is offline. If it is not, + * and the 'force' flag is TRUE, tell STMF to offline it + * before removing from the configuration. + */ + ret = stmfDevidFromIscsiName(ptgt->tgt_name, &devid); + if (ret != STMF_STATUS_SUCCESS) { + /* can't happen? */ + return (EINVAL); + } + + ret = stmfGetTargetProperties(&devid, &props); + if (ret == STMF_STATUS_SUCCESS) { + /* + * only other return is STMF_ERROR_NOT_FOUND, which + * means we don't have to offline it. + */ + if (props.status == STMF_TARGET_PORT_ONLINE) { + if (!force) { + return (EBUSY); + } + ret = stmfOfflineTarget(&devid); + if (ret != 0) { + return (EBUSY); + } + } + } + + if (prev) { + prev->tgt_next = ptgt->tgt_next; + } else { + /* first one on the list */ + cfg->config_tgt_list = ptgt->tgt_next; + } + + ptgt->tgt_next = NULL; /* Only free this target */ + + cfg->config_tgt_count--; + it_tgt_free(ptgt); + + return (0); +} + +/* + * Function: it_tgt_free() + * + * Frees an it_tgt_t structure. If tgt_next is not NULL, frees + * all structures in the list. + */ +void +it_tgt_free(it_tgt_t *tgt) +{ + it_tgt_free_cmn(tgt); +} + +/* + * Function: it_tpgt_create() + * + * Allocate and create an it_tpgt_t structure representing a new iSCSI + * target portal group tag. The new it_tpgt_t structure is added to the + * target tpgt list (tgt_tpgt_list) in the it_tgt_t structure. The new + * target portal group tag will not be instantiated until the modified + * configuration is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tgt Pointer to the iSCSI target structure associated + * with the target portal group tag + * tpgt Pointer to a target portal group tag structure + * tpg_name The name of the TPG to be associated with this TPGT + * tpgt_tag 16-bit numerical identifier for this TPGT. If + * tpgt_tag is '0', this function will choose the + * tag number. If tpgt_tag is >0, and the requested + * tag is determined to be in use, another value + * will be chosen. + * + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid parameter + * EEXIST Specified tag name is already used. + * E2BIG No available tag numbers + */ +int +it_tpgt_create(it_config_t *cfg, it_tgt_t *tgt, it_tpgt_t **tpgt, + char *tpg_name, uint16_t tpgt_tag) +{ + it_tpgt_t *ptr = NULL; + it_tpgt_t *cfgt; + char tagid_used[MAXTAG + 1]; + uint16_t tagid = ISCSIT_DEFAULT_TPGT; + + if (!cfg || !tgt || !tpgt || !tpg_name) { + return (EINVAL); + } + + (void) memset(&(tagid_used[0]), 0, sizeof (tagid_used)); + + /* + * Make sure this name and/or tag isn't already on the list + * At the same time, capture all tag ids in use for this target + * + * About tag numbering -- since tag numbers are used by + * the iSCSI protocol, we should be careful about reusing + * them too quickly. Start with a value greater than the + * highest one currently defined. If current == MAXTAG, + * just find an unused tag. + */ + cfgt = tgt->tgt_tpgt_list; + while (cfgt != NULL) { + tagid_used[cfgt->tpgt_tag] = 1; + + if (strcmp(tpg_name, cfgt->tpgt_tpg_name) == 0) { + return (EEXIST); + } + + if (cfgt->tpgt_tag > tagid) { + tagid = cfgt->tpgt_tag; + } + + cfgt = cfgt->tpgt_next; + } + + if ((tpgt_tag > ISCSIT_DEFAULT_TPGT) && (tpgt_tag < MAXTAG) && + (tagid_used[tpgt_tag] == 0)) { + /* ok to use requested */ + tagid = tpgt_tag; + } else if (tagid == MAXTAG) { + /* + * The highest value is used, find an available id. + */ + tagid = ISCSIT_DEFAULT_TPGT + 1; + for (; tagid < MAXTAG; tagid++) { + if (tagid_used[tagid] == 0) { + break; + } + } + if (tagid >= MAXTAG) { + return (E2BIG); + } + } else { + /* next available ID */ + tagid++; + } + + ptr = calloc(1, sizeof (it_tpgt_t)); + if (!ptr) { + return (ENOMEM); + } + + (void) strlcpy(ptr->tpgt_tpg_name, tpg_name, + sizeof (ptr->tpgt_tpg_name)); + ptr->tpgt_generation = 1; + ptr->tpgt_tag = tagid; + + ptr->tpgt_next = tgt->tgt_tpgt_list; + tgt->tgt_tpgt_list = ptr; + tgt->tgt_tpgt_count++; + tgt->tgt_generation++; + + *tpgt = ptr; + + return (0); +} + +/* + * Function: it_tpgt_delete() + * + * Delete the target portal group tag represented by 'tpgt', where + * 'tpgt' is an existing is_tpgt_t structure within the target 'tgt'. + * The target portal group tag removal will not take effect until the + * modified configuration is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tgt Pointer to the iSCSI target structure associated + * with the target portal group tag + * tpgt Pointer to a target portal group tag structure + */ +void +it_tpgt_delete(it_config_t *cfg, it_tgt_t *tgt, it_tpgt_t *tpgt) +{ + it_tpgt_t *ptr; + it_tpgt_t *prev = NULL; + + if (!cfg || !tgt || !tpgt) { + return; + } + + ptr = tgt->tgt_tpgt_list; + while (ptr) { + if (ptr->tpgt_tag == tpgt->tpgt_tag) { + break; + } + prev = ptr; + ptr = ptr->tpgt_next; + } + + if (!ptr) { + return; + } + + if (prev) { + prev->tpgt_next = ptr->tpgt_next; + } else { + tgt->tgt_tpgt_list = ptr->tpgt_next; + } + ptr->tpgt_next = NULL; + + tgt->tgt_tpgt_count--; + tgt->tgt_generation++; + + it_tpgt_free(ptr); +} + +/* + * Function: it_tpgt_free() + * + * Deallocates resources of an it_tpgt_t structure. If tpgt->next + * is not NULL, frees all members of the list. + */ +void +it_tpgt_free(it_tpgt_t *tpgt) +{ + it_tpgt_free_cmn(tpgt); +} + +/* + * Function: it_tpg_create() + * + * Allocate and create an it_tpg_t structure representing a new iSCSI + * target portal group. The new it_tpg_t structure is added to the global + * tpg list (cfg_tgt_list) in the it_config_t structure. The new target + * portal group will not be instantiated until the modified configuration + * is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tpg Pointer to the it_tpg_t structure representing + * the target portal group + * tpg_name Identifier for the target portal group + * portal_ip_port A string containing an appropriatedly formatted + * IP address:port. Both IPv4 and IPv6 addresses are + * permitted. This value becomes the first portal in + * the TPG -- applications can add additional values + * using it_portal_create() before committing the TPG. + * Return Values: + * 0 Success + * ENOMEM Cannot allocate resources + * EINVAL Invalid parameter + * EEXIST Requested portal in use by another target portal + * group + */ +int +it_tpg_create(it_config_t *cfg, it_tpg_t **tpg, char *tpg_name, + char *portal_ip_port) +{ + int ret; + it_tpg_t *ptr; + it_portal_t *portal = NULL; + + if (!cfg || !tpg || !tpg_name || !portal_ip_port) { + return (EINVAL); + } + + *tpg = NULL; + + ptr = cfg->config_tpg_list; + while (ptr) { + if (strcmp(tpg_name, ptr->tpg_name) == 0) { + break; + } + ptr = ptr->tpg_next; + } + + if (ptr) { + return (EEXIST); + } + + ptr = calloc(1, sizeof (it_tpg_t)); + if (!ptr) { + return (ENOMEM); + } + + ptr->tpg_generation = 1; + (void) strlcpy(ptr->tpg_name, tpg_name, sizeof (ptr->tpg_name)); + + /* create the portal */ + ret = it_portal_create(cfg, ptr, &portal, portal_ip_port); + if (ret != 0) { + free(ptr); + return (ret); + } + + ptr->tpg_next = cfg->config_tpg_list; + cfg->config_tpg_list = ptr; + cfg->config_tpg_count++; + + *tpg = ptr; + + return (0); +} + +/* + * Function: it_tpg_delete() + * + * Delete target portal group represented by 'tpg', where 'tpg' is an + * existing it_tpg_t structure within the global configuration 'cfg'. + * The target portal group removal will not take effect until the + * modified configuration is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tpg Pointer to the it_tpg_t structure representing + * the target portal group + * force Remove this target portal group even if it's + * associated with one or more targets. + * + * Return Values: + * 0 Success + * EINVAL Invalid parameter + * EBUSY Portal group associated with one or more targets. + */ +int +it_tpg_delete(it_config_t *cfg, it_tpg_t *tpg, boolean_t force) +{ + it_tpg_t *ptr; + it_tpg_t *prev = NULL; + it_tgt_t *tgt; + it_tpgt_t *tpgt; + it_tpgt_t *ntpgt; + + if (!cfg || !tpg) { + return (EINVAL); + } + + ptr = cfg->config_tpg_list; + while (ptr) { + if (strcmp(ptr->tpg_name, tpg->tpg_name) == 0) { + break; + } + prev = ptr; + ptr = ptr->tpg_next; + } + + if (!ptr) { + return (0); + } + + /* + * See if any targets are using this portal group. + * If there are, and the force flag is not set, fail. + */ + tgt = cfg->config_tgt_list; + while (tgt) { + tpgt = tgt->tgt_tpgt_list; + while (tpgt) { + ntpgt = tpgt->tpgt_next; + + if (strcmp(tpgt->tpgt_tpg_name, tpg->tpg_name) + == 0) { + if (!force) { + return (EBUSY); + } + it_tpgt_delete(cfg, tgt, tpgt); + } + + tpgt = ntpgt; + } + tgt = tgt->tgt_next; + } + + /* Now that it's not in use anywhere, remove the TPG */ + if (prev) { + prev->tpg_next = ptr->tpg_next; + } else { + cfg->config_tpg_list = ptr->tpg_next; + } + ptr->tpg_next = NULL; + + cfg->config_tpg_count--; + + it_tpg_free(ptr); + + return (0); +} + +/* + * Function: it_tpg_free() + * + * Deallocates resources associated with an it_tpg_t structure. + * If tpg->next is not NULL, frees all members of the list. + */ +void +it_tpg_free(it_tpg_t *tpg) +{ + it_tpg_free_cmn(tpg); +} + +/* + * Function: it_portal_create() + * + * Add an it_portal_t structure presenting a new portal to the specified + * target portal group. The change to the target portal group will not take + * effect until the modified configuration is committed by calling + * it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configration obtained from + * it_config_load() + * tpg Pointer to the it_tpg_t structure representing the + * target portal group + * portal Pointer to the it_portal_t structure representing + * the portal + * portal_ip_port A string containing an appropriately formatted + * IP address or IP address:port in either IPv4 or + * IPv6 format. + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid parameter + * EEXIST Portal already configured for another portal group + */ +int +it_portal_create(it_config_t *cfg, it_tpg_t *tpg, it_portal_t **portal, + char *portal_ip_port) +{ + struct sockaddr_storage sa; + it_portal_t *ptr; + it_tpg_t *ctpg = NULL; + + if (!cfg || !tpg || !portal || !portal_ip_port) { + return (EINVAL); + } + + if ((it_common_convert_sa(portal_ip_port, &sa, ISCSI_LISTEN_PORT)) + == NULL) { + return (EINVAL); + } + + /* Check that this portal doesn't appear in any other tag */ + ctpg = cfg->config_tpg_list; + while (ctpg) { + ptr = ctpg->tpg_portal_list; + for (; ptr != NULL; ptr = ptr->next) { + if (it_sa_compare(&(ptr->portal_addr), &sa) != 0) { + continue; + } + + /* + * Existing in the same group is not an error, + * but don't add it again. + */ + if (strcmp(ctpg->tpg_name, tpg->tpg_name) == 0) { + return (0); + } else { + /* Not allowed */ + return (EEXIST); + } + } + ctpg = ctpg->tpg_next; + } + + ptr = calloc(1, sizeof (it_portal_t)); + if (!ptr) { + return (ENOMEM); + } + + (void) memcpy(&(ptr->portal_addr), &sa, + sizeof (struct sockaddr_storage)); + ptr->next = tpg->tpg_portal_list; + tpg->tpg_portal_list = ptr; + tpg->tpg_portal_count++; + tpg->tpg_generation++; + + return (0); +} + +/* + * Function: it_portal_delete() + * + * Remove the specified portal from the specified target portal group. + * The portal removal will not take effect until the modified configuration + * is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configration obtained from + * it_config_load() + * tpg Pointer to the it_tpg_t structure representing the + * target portal group + * portal Pointer to the it_portal_t structure representing + * the portal + */ +void +it_portal_delete(it_config_t *cfg, it_tpg_t *tpg, it_portal_t *portal) +{ + it_portal_t *ptr; + it_portal_t *prev; + + if (!cfg || !tpg || !portal) { + return; + } + + ptr = tpg->tpg_portal_list; + while (ptr) { + if (memcmp(&(ptr->portal_addr), &(portal->portal_addr), + sizeof (ptr->portal_addr)) == 0) { + break; + } + prev = ptr; + ptr = ptr->next; + } + + if (!ptr) { + return; + } + + if (prev) { + prev->next = ptr->next; + } else { + tpg->tpg_portal_list = ptr->next; + } + tpg->tpg_portal_count--; + tpg->tpg_generation++; + + free(ptr); +} + +/* + * Function: it_ini_create() + * + * Add an initiator context to the global configuration. The new + * initiator context will not be instantiated until the modified + * configuration is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configration obtained from + * it_config_load() + * ini Pointer to the it_ini_t structure representing + * the initiator context. + * ini_node_name The iSCSI node name of the remote initiator. + * + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid parameter. + * EFAULT Invalid initiator name + */ +int +it_ini_create(it_config_t *cfg, it_ini_t **ini, char *ini_node_name) +{ + it_ini_t *ptr; + + if (!cfg || !ini || !ini_node_name) { + return (EINVAL); + } + + /* + * Ensure this is a valid ini name + */ + if (!validate_iscsi_name(ini_node_name)) { + return (EFAULT); + } + + ptr = cfg->config_ini_list; + while (ptr) { + if (strcmp(ptr->ini_name, ini_node_name) == 0) { + break; + } + ptr = ptr->ini_next; + } + + if (ptr) { + return (EEXIST); + } + + ptr = calloc(1, sizeof (it_ini_t)); + if (!ptr) { + return (ENOMEM); + } + + (void) strlcpy(ptr->ini_name, ini_node_name, sizeof (ptr->ini_name)); + ptr->ini_generation = 1; + /* nvlist for props? */ + + ptr->ini_next = cfg->config_ini_list; + cfg->config_ini_list = ptr; + cfg->config_ini_count++; + + *ini = ptr; + + return (0); +} + +/* + * Function: it_ini_setprop() + * + * Validate the provided property list and set the initiator properties. + * If errlist is not NULL, returns detailed errors for each property + * that failed. The format for errorlist is key = property, + * value = error string. + * + * Parameters: + * + * ini The initiator being updated. + * proplist nvlist_t containing properties for this target. + * errlist (optional) nvlist_t of errors encountered when + * validating the properties. + * + * Return Values: + * 0 Success + * EINVAL Invalid property + * + */ +int +it_ini_setprop(it_ini_t *ini, nvlist_t *proplist, nvlist_t **errlist) +{ + int ret; + nvlist_t *iprops = NULL; + char *val = NULL; + + if (!ini || !proplist) { + return (EINVAL); + } + + if (errlist) { + (void) nvlist_alloc(errlist, 0, 0); + } + + /* + * copy the existing properties, merge, then validate + * the merged properties before committing them. + */ + if (ini->ini_properties) { + ret = nvlist_dup(ini->ini_properties, &iprops, 0); + } else { + ret = nvlist_alloc(&iprops, NV_UNIQUE_NAME, 0); + } + + if (ret == 0) { + ret = nvlist_merge(iprops, proplist, 0); + } + + /* unset chap username if requested */ + if ((nvlist_lookup_string(proplist, PROP_CHAP_USER, &val)) == 0) { + if (strcasecmp(val, "none") == 0) { + (void) nvlist_remove_all(iprops, PROP_CHAP_USER); + } + } + + /* base64 encode the CHAP secret, if it's changed */ + if ((nvlist_lookup_string(proplist, PROP_CHAP_SECRET, &val)) == 0) { + char bsecret[MAX_BASE64_LEN]; + + ret = it_val_pass(PROP_CHAP_SECRET, val, *errlist); + if (ret == 0) { + (void) memset(bsecret, 0, MAX_BASE64_LEN); + + ret = iscsi_binary_to_base64_str((uint8_t *)val, + strlen(val), bsecret, MAX_BASE64_LEN); + + if (ret == 0) { + /* replace the value in the nvlist */ + ret = nvlist_add_string(iprops, + PROP_CHAP_SECRET, bsecret); + } + } + } + + if (ret == 0) { + ret = it_validate_iniprops(iprops, *errlist); + } + + if (ret != 0) { + if (iprops) { + nvlist_free(iprops); + } + return (ret); + } + + if (ini->ini_properties) { + nvlist_free(ini->ini_properties); + } + ini->ini_properties = iprops; + + return (0); +} + +/* + * Function: it_ini_delete() + * + * Remove the specified initiator context from the global configuration. + * The removal will not take effect until the modified configuration is + * committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configration obtained from + * it_config_load() + * ini Pointer to the it_ini_t structure representing + * the initiator context. + */ +void +it_ini_delete(it_config_t *cfg, it_ini_t *ini) +{ + it_ini_t *ptr; + it_ini_t *prev = NULL; + + if (!cfg || !ini) { + return; + } + + ptr = cfg->config_ini_list; + while (ptr) { + if (strcmp(ptr->ini_name, ini->ini_name) == 0) { + break; + } + prev = ptr; + ptr = ptr->ini_next; + } + + if (!ptr) { + return; + } + + if (prev) { + prev->ini_next = ptr->ini_next; + } else { + cfg->config_ini_list = ptr->ini_next; + } + + ptr->ini_next = NULL; /* Only free this initiator */ + + cfg->config_ini_count--; + + it_ini_free(ptr); +} + +/* + * Function: it_ini_free() + * + * Deallocates resources of an it_ini_t structure. If ini->next is + * not NULL, frees all members of the list. + */ +void +it_ini_free(it_ini_t *ini) +{ + it_ini_free_cmn(ini); +} + +/* + * Goes through the target property list and validates + * each entry. If errs is non-NULL, will return explicit errors + * for each property that fails validation. + */ +static int +it_validate_tgtprops(nvlist_t *nvl, nvlist_t *errs) +{ + int errcnt = 0; + nvpair_t *nvp = NULL; + data_type_t nvtype; + char *name; + char *val; + char *auth = NULL; + + if (!nvl) { + return (0); + } + + while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) { + name = nvpair_name(nvp); + nvtype = nvpair_type(nvp); + + if (!name) { + continue; + } + + val = NULL; + if (strcmp(name, PROP_TARGET_CHAP_USER) == 0) { + if (nvtype != DATA_TYPE_STRING) { + PROPERR(errs, name, + gettext("must be a string value")); + errcnt++; + continue; + } + } else if (strcmp(name, PROP_TARGET_CHAP_SECRET) == 0) { + /* + * must be between 12 and 255 chars in cleartext. + * will be base64 encoded when it's set. + */ + if (nvtype == DATA_TYPE_STRING) { + (void) nvpair_value_string(nvp, &val); + } + + if (!val) { + PROPERR(errs, name, + gettext("must be a string value")); + errcnt++; + continue; + } + } else if (strcmp(name, PROP_ALIAS) == 0) { + if (nvtype != DATA_TYPE_STRING) { + PROPERR(errs, name, + gettext("must be a string value")); + errcnt++; + continue; + } + } else if (strcmp(name, PROP_AUTH) == 0) { + if (nvtype == DATA_TYPE_STRING) { + val = NULL; + (void) nvpair_value_string(nvp, &val); + } + + if (!val) { + PROPERR(errs, name, + gettext("must be a string value")); + errcnt++; + continue; + } + if ((strcmp(val, PA_AUTH_NONE) != 0) && + (strcmp(val, PA_AUTH_CHAP) != 0) && + (strcmp(val, PA_AUTH_RADIUS) != 0) && + (strcmp(val, "default") != 0)) { + PROPERR(errs, val, gettext( + "must be none, chap, radius or default")); + errcnt++; + } + auth = val; + continue; + } else if (strcmp(name, PROP_OLD_TARGET_NAME) == 0) { + continue; + } else { + /* unrecognized property */ + PROPERR(errs, name, gettext("unrecognized property")); + errcnt++; + } + } + + if (errcnt) { + return (EINVAL); + } + + /* if auth is being set to default, remove from this nvlist */ + if (auth && (strcmp(auth, "default") == 0)) { + (void) nvlist_remove_all(nvl, PROP_AUTH); + } + + return (0); +} + +/* + * Goes through the config property list and validates + * each entry. If errs is non-NULL, will return explicit errors + * for each property that fails validation. + */ +static int +it_validate_configprops(nvlist_t *nvl, nvlist_t *errs) +{ + int errcnt = 0; + nvpair_t *nvp = NULL; + data_type_t nvtype; + char *name; + char *val; + struct sockaddr_storage sa; + char *auth = NULL; + + if (!nvl) { + return (0); + } + + while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) { + name = nvpair_name(nvp); + nvtype = nvpair_type(nvp); + + if (!name) { + continue; + } + + val = NULL; + + /* prefetch string value as we mostly need it */ + if (nvtype == DATA_TYPE_STRING) { + (void) nvpair_value_string(nvp, &val); + } + + if (strcmp(name, PROP_ALIAS) == 0) { + if (!val) { + PROPERR(errs, name, + gettext("must be a string value")); + errcnt++; + } + } else if (strcmp(name, PROP_AUTH) == 0) { + if (!val) { + PROPERR(errs, name, + gettext("must be a string value")); + errcnt++; + continue; + } + + if ((strcmp(val, PA_AUTH_NONE) != 0) && + (strcmp(val, PA_AUTH_CHAP) != 0) && + (strcmp(val, PA_AUTH_RADIUS) != 0)) { + PROPERR(errs, PROP_AUTH, + gettext("must be none, chap or radius")); + errcnt++; + } + + auth = val; + + } else if (strcmp(name, PROP_ISNS_ENABLED) == 0) { + if (nvtype != DATA_TYPE_BOOLEAN_VALUE) { + PROPERR(errs, name, + gettext("must be a boolean value")); + errcnt++; + } + } else if (strcmp(name, PROP_ISNS_SERVER) == 0) { + char **arr = NULL; + uint32_t acount = 0; + + (void) nvlist_lookup_string_array(nvl, name, + &arr, &acount); + + while (acount > 0) { + if (strcasecmp(arr[acount - 1], "none") == 0) { + break; + } + if ((it_common_convert_sa(arr[acount - 1], + &sa, 0)) == NULL) { + PROPERR(errs, arr[acount - 1], + gettext("invalid address")); + errcnt++; + } + acount--; + } + + } else if (strcmp(name, PROP_RADIUS_SECRET) == 0) { + if (!val) { + PROPERR(errs, name, + gettext("must be a string value")); + errcnt++; + continue; + } + } else if (strcmp(name, PROP_RADIUS_SERVER) == 0) { + struct sockaddr_storage sa; + + if (!val) { + PROPERR(errs, name, + gettext("must be a string value")); + errcnt++; + continue; + } + + if ((it_common_convert_sa(val, &sa, + DEFAULT_RADIUS_PORT)) == NULL) { + PROPERR(errs, name, + gettext("invalid address")); + errcnt++; + } else { + /* + * rewrite this property to ensure port + * number is added. + */ + char *rad = NULL; + + if (sockaddr_to_str(&sa, &rad) == 0) { + (void) nvlist_add_string(nvl, + name, rad); + } + } + } else { + /* unrecognized property */ + PROPERR(errs, name, gettext("unrecognized property")); + errcnt++; + } + } + + /* + * if auth = radius, ensure radius server & secret are set. + */ + if (auth) { + if (strcmp(auth, PA_AUTH_RADIUS) == 0) { + /* need server & secret for radius */ + if (!nvlist_exists(nvl, PROP_RADIUS_SERVER)) { + PROPERR(errs, PROP_RADIUS_SERVER, + gettext("missing required property")); + errcnt++; + } + if (!nvlist_exists(nvl, PROP_RADIUS_SECRET)) { + PROPERR(errs, PROP_RADIUS_SECRET, + gettext("missing required property")); + errcnt++; + } + } + } + + if (errcnt) { + return (EINVAL); + } + + return (0); +} + +/* + * Goes through the ini property list and validates + * each entry. If errs is non-NULL, will return explicit errors + * for each property that fails validation. + */ +static int +it_validate_iniprops(nvlist_t *nvl, nvlist_t *errs) +{ + int errcnt = 0; + nvpair_t *nvp = NULL; + data_type_t nvtype; + char *name; + char *val; + + if (!nvl) { + return (0); + } + + while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) { + name = nvpair_name(nvp); + nvtype = nvpair_type(nvp); + + if (!name) { + continue; + } + + if (strcmp(name, PROP_CHAP_USER) == 0) { + if (nvtype != DATA_TYPE_STRING) { + PROPERR(errs, name, + gettext("must be a string value")); + errcnt++; + continue; + } + } else if (strcmp(name, PROP_CHAP_SECRET) == 0) { + /* + * must be between 12 and 255 chars in cleartext. + * will be base64 encoded when it's set. + */ + if (nvtype == DATA_TYPE_STRING) { + val = NULL; + (void) nvpair_value_string(nvp, &val); + } + + if (!val) { + PROPERR(errs, name, + gettext("must be a string value")); + errcnt++; + continue; + } + } else { + /* unrecognized property */ + PROPERR(errs, name, gettext("unrecognized property")); + errcnt++; + } + } + + if (errcnt) { + return (EINVAL); + } + + return (0); +} + +static int +it_iqn_generate(char *iqn_buf, int iqn_buf_len, char *opt_iqn_suffix) +{ + int ret; + uuid_t id; + char id_str[UUID_PRINTABLE_STRING_LENGTH]; + + uuid_generate_random(id); + uuid_unparse(id, id_str); + + if (opt_iqn_suffix) { + ret = snprintf(iqn_buf, iqn_buf_len, "iqn.1986-03.com.sun:" + "%02d:%s.%s", TARGET_NAME_VERS, id_str, opt_iqn_suffix); + } else { + ret = snprintf(iqn_buf, iqn_buf_len, "iqn.1986-03.com.sun:" + "%02d:%s", TARGET_NAME_VERS, id_str); + } + + if (ret > iqn_buf_len) { + return (1); + } + + return (0); +} + +static int +it_val_pass(char *name, char *val, nvlist_t *e) +{ + size_t sz; + + if (!name || !val) { + return (EINVAL); + } + + /* + * must be at least 12 chars and less than 256 chars cleartext. + */ + sz = strlen(val); + + /* + * Since we will be automatically encoding secrets we don't really + * need the prefix anymore. + */ + if (sz < 12) { + PROPERR(e, name, gettext("secret too short")); + } else if (sz > 255) { + PROPERR(e, name, gettext("secret too long")); + } else { + /* all is well */ + return (0); + } + + return (1); +} + +/* + * Function: validate_iscsi_name() + * + * Ensures the passed-in string is a valid IQN or EUI iSCSI name + * + */ +boolean_t +validate_iscsi_name(char *in_name) +{ + size_t in_len; + int i; + char month[3]; + + if (in_name == NULL) { + return (B_FALSE); + } + + in_len = strlen(in_name); + if (in_len < 12) { + return (B_FALSE); + } + + if (strncasecmp(in_name, "iqn.", 4) == 0) { + /* + * IQN names are iqn.yyyy-mm. + */ + if ((!isdigit(in_name[4])) || + (!isdigit(in_name[5])) || + (!isdigit(in_name[6])) || + (!isdigit(in_name[7])) || + (in_name[8] != '-') || + (!isdigit(in_name[9])) || + (!isdigit(in_name[10])) || + (in_name[11] != '.')) { + return (B_FALSE); + } + + (void) strncpy(month, &(in_name[9]), 2); + month[2] = '\0'; + + i = atoi(month); + if ((i < 0) || (i > 12)) { + return (B_FALSE); + } + + /* Finally, validate the overall length, in wide chars */ + in_len = mbstowcs(NULL, in_name, 0); + if (in_len > ISCSI_NAME_LEN_MAX) { + return (B_FALSE); + } + } else if (strncasecmp(in_name, "eui.", 4) == 0) { + /* + * EUI names are "eui." + 16 hex chars + */ + if (in_len != 20) { + return (B_FALSE); + } + + for (i = 4; i < in_len; i++) { + if (!isxdigit(in_name[i])) { + return (B_FALSE); + } + } + } else { + return (B_FALSE); + } + + return (B_TRUE); +} diff --git a/usr/src/lib/libiscsit/common/libiscsit.h b/usr/src/lib/libiscsit/common/libiscsit.h new file mode 100644 index 000000000000..598a46a09ce6 --- /dev/null +++ b/usr/src/lib/libiscsit/common/libiscsit.h @@ -0,0 +1,747 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LIBISCSIT_H +#define _LIBISCSIT_H + +#ifndef _KERNEL +#include +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISCSIT_MODNAME "iscsit" +#define ISCSIT_NODE "/devices/pseudo/iscsit@0:iscsit" + +#define MAX_TPGT 256 +#define CFG_TPGTLIST "tpgt-list" + +/* + * Object Hierarchy + * + * _______________________ + * | | + * | iSCSI Target Config | + * | it_config_t | + * |_______________________| + * | | + * | | + * | | ________ ________ ________ + * | | | | | | | | + * | | | Target |-->| Target |-- - - -->| Target | + * | | |________| |________| |________| + * | | | + * | | | + * | | | + * | | | ______ ______ + * | | | | | | | + * | | +----->| TPGT |-- - - -->| TPGT | + * | | |______| |______| + * | | | | + * | +--+ | | + * | | _______ _______ | ______ | + * | | | | | |<--+ | |<--+ + * | +->| TPG |-->| TPG |-- - - -->| TPG | + * | |_______| |_______| |______| + * | + * | ___________ ___________ ___________ + * | | | | | | | + * +---->| Initiator |-->| Initiator |-- - - -->| Initiator | + * | Context | | Context | | Context | + * |___________| |___________| |___________| + * + * + * it_config_t includes a list of global properties + * + * Targets include a list of properties which override the global properties + * if set + * + * Initiators also include a list of properties but never inherit properties + * from the global config. + */ + +/* Maximum size of a Target Portal Group name */ +#define MAX_TPG_NAMELEN 256 /* XXX */ + +/* Maximum size of an iSCSI Target Node name */ +#define MAX_ISCSI_NODENAMELEN 256 /* XXX */ + +/* + * A target portal group tag is a binding between a target and a target + * portal group along with a numerical value associated with that binding. + * The numerical identifier is used as the 'target portal group tag' defined + * in RFC3720. + * + * tpgt_tpg_name The name of the target portal group associated with + * this target portal group tag. + * tpgt_generation Generation number which is incremented each time the + * structure changes. + * tpgt_next Next target portal group tag in th list of target portal + * group tags. If tpgt_next is NUL, then this is the last + * target portal group in the list. + * tpgt_tag A numerical identifier that uniquely identifies a + * target portal group within the associated target node. + */ +typedef struct it_tpgt_s { + char tpgt_tpg_name[MAX_TPG_NAMELEN]; + uint64_t tpgt_generation; + struct it_tpgt_s *tpgt_next; + uint16_t tpgt_tag; +} it_tpgt_t; + +/* + * An iSCSI target node is represented by an it_tgt_structure. Each + * target node includes a list of associated target portal group tags + * and a list of properties. + * + * tgt_name The iSCSI target node name in either IQN or EUI + * format (see RFC3720). + * tgt_generation Generation number which is incremented each time + * the structure changes. + * tgt_next Next target in the list of targets. If tgt_next + * is NULL, then this is the last target in the list. + * tgt_tpgt_list A linked list representing the current target + * portal group tags associated with this target. + * tgt_tpgt_count The number of currently defined target portal + * group tags. + * tgt_properties An nvlist representation of the properties + * associated with this target. This list can be + * manipulated using libnvpair(3lib), and should be + * validated and stored using it_tgt_setprop(). + * + * Target nvlist Properties: + * + * nvlist Key Type Valid Values + * -------------------------------------------------------------------- + * targetchapuser string any string or "none" to remove + * targetchapsecret string string of at least 12 characters + * but not more than 255 characters. + * secret will be base64 encoded when + * stored. + * alias string any string or "none" to remove + * auth string "radius", "chap", or "none" + * + */ +typedef struct it_tgt_s { + char tgt_name[MAX_ISCSI_NODENAMELEN]; + uint64_t tgt_generation; + struct it_tgt_s *tgt_next; + it_tpgt_t *tgt_tpgt_list; + uint32_t tgt_tpgt_count; + nvlist_t *tgt_properties; +} it_tgt_t; + +/* + * A target portal is represented by an IP address and a listening + * TCP port. + * + * portal_addr sockaddr_storage structure representing the + * IPv4 or IPv6 address and TCP port associated + * with the portal. + * portal_next Next portal in the list of portals. If + * portal_next is NULL, this is the last portal + * in the list. + */ +typedef struct it_portal_s { + struct sockaddr_storage portal_addr; + struct it_portal_s *next; +} it_portal_t; + +/* + * A portal is an IP address and TCP port and a portal group is a set + * of portals. Each defined portal belongs to exactly one portal group. + * Applications can associate a target portal group with a particular + * target using a target portal group name. Initiators can only connect + * to targets through the portals associated with the target's target + * portal group tags. + * + * tpg_name Identifier for the target portal group. + * tpg_generation Generation number which is incremented each + * time this structure changes. + * tpg_next Next target portal group in the list of target + * portal groups. If tpg_next is NULL, this is the + * last target portal group in the list. + * tpg_portal_count Number of it_portal_t structures in the list. + * tpg_portal_list Linked list of it_portal_t structures. + */ +typedef struct it_tpg_s { + char tpg_name[MAX_TPG_NAMELEN]; + uint64_t tpg_generation; + struct it_tpg_s *tpg_next; + uint32_t tpg_portal_count; + it_portal_t *tpg_portal_list; +} it_tpg_t; + +/* + * A context representing a remote iSCSI initiator node. The purpose + * of this structure is to maintain information specific to a remote + * initiator such as the CHAP username and CHAP secret. + * + * ini_name the iSCSI node name of the remote initiator. + * ini_generation Generation number which is incremented each + * time this structure changes. + * ini_next Next initiator in the list of initiators. + * If ini_next is NULL, this is the last initiator + * in the list. + * ini_properties Name/Value list containing the properties + * associated with the initiator context. This list + * can be manipulated using libnvpair(3lib), and should + * be validated and stored using it_ini_setprop(). + * + * Initiator nvlist Properties: + * + * nvlist Key Type Valid Values + * -------------------------------------------------------------------- + * chapuser string any string + * chapsecret string string of at least 12 characters + * but not more than 255 characters. + * secret will be base64 encoded when + * stored. + */ +typedef struct it_ini_s { + char ini_name[MAX_ISCSI_NODENAMELEN]; + uint64_t ini_generation; + struct it_ini_s *ini_next; + nvlist_t *ini_properties; +} it_ini_t; + + +/* + * This structure represents a complete configuration for the iscsit + * port provider. In addition to the global configuration, it_config_t + * includes lists of child objects including targets, target portal + * groups and initiator contexts. Each object includes a "generation" + * value which is used by the iscsit kernel driver to identify changes + * from one configuration update to the next. + * + * stmf_token A uint64_t that contains the value returned from a + * successful call to stmfGetProviderDataProt(3STMF). + * This token is used to verify that the configuration + * data persistently stored in COMSTAR has not been + * modified since this version was loaded. + * config_version Version number for this configuration structure + * config_tgt_list Linked list of target contexts representing the + * currently defined targets. Applications can add + * targets to or remove targets from this list using + * the it_tgt_create and it_tgt_delete functions. + * config_tgt_count The number of currently defined targets. + * config_tpg_list Linked list of target portal group contexts. + * Applications can add or remove target portal groups + * to/from this list using the it_tpg_create and + * it_tpg_delete functions. + * config_tpg_count The number of currently defined target portal groups + * config_ini_list Linked list of initiator contexts. Applications + * can add initiator contexts or remove initiator + * contexts from this list using the it_ini_create + * and it_ini_delete functions. + * config_ini_count The number of currently defined initiator contexts. + * config_global_properties + * Name/Value list representing the current global + * property settings. This list can be manipulated + * using libnvpair(3lib), and should be validated + * and stored using it_config_setprop(). + * config_isns_svr_list + * Linked list of currently defined iSNS servers. + * Applications can add or remove iSNS servers by + * using the it_config_setprop() function and changing + * the array of iSNS servers stored in the "isnsserver" + * property. + * config_isns_svr_count + * The number of currently defined iSNS servers. + * + * Global nvlist Properties: + * + * nvlist Key Type Valid Values + * -------------------------------------------------------------------- + * alias string any string + * auth string "radius", "chap", or "none" + * isns boolean B_TRUE, B_FALSE + * isnsserver string array Array of portal specifications of + * the form IPaddress:port. Port + * is optional; if not specified, the + * default iSNS port number of 3205 will + * be used. IPv6 addresses should + * be enclosed in square brackets '[' ']'. + * If "none" is specified, all defined + * iSNS servers will be removed from the + * configuration. + * radiusserver string IPaddress:port specification as + * described for 'isnsserver'. + * radiussecret string string of at least 12 characters + * but not more than 255 characters. + * secret will be base64 encoded when + * stored. + */ +typedef struct it_config_s { + uint64_t stmf_token; + uint32_t config_version; + it_tgt_t *config_tgt_list; + uint32_t config_tgt_count; + it_tpg_t *config_tpg_list; + uint32_t config_tpg_count; + it_ini_t *config_ini_list; + uint32_t config_ini_count; + it_portal_t *config_isns_svr_list; + uint32_t config_isns_svr_count; + nvlist_t *config_global_properties; +} it_config_t; + +/* + * Function: it_config_load() + * + * Allocate and create an it_config_t structure representing the + * current iSCSI configuration. This structure is compiled using + * the 'provider' data returned by stmfGetProviderData(). If there + * is no provider data associated with iscsit, the it_config_t + * structure will be set to a default configuration. + * + * Parameters: + * cfg A C representation of the current iSCSI configuration + * + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid parameter + */ +int +it_config_load(it_config_t **cfg); + +/* + * Function: it_config_commit() + * + * Informs the iscsit service that the configuration has changed and + * commits the new configuration to persistent store by calling + * stmfSetProviderData. This function can be called multiple times + * during a configuration sequence if necessary. + * + * Parameters: + * cfg A C representation of the current iSCSI configuration + * + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid it_config_t structure + * STMF_ERROR_SERVICE_DATA_VERSION Configuration was updated by another + * client. See stmfSetProviderDataProt(). + */ +int +it_config_commit(it_config_t *cfg); + +/* + * Function: it_config_setprop() + * + * Validate the provided property list and set the global properties + * for iSCSI Target. If errlist is not NULL, returns detailed + * errors for each property that failed. The format for errorlist + * is key = property, value = error string. + * + * Parameters: + * + * cfg The current iSCSI configuration obtained from + * it_config_load() + * proplist nvlist_t containing properties for this target. + * errlist (optional) nvlist_t of errors encountered when + * validating the properties. + * + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid property + * + */ +int +it_config_setprop(it_config_t *cfg, nvlist_t *proplist, nvlist_t **errlist); + +/* + * Function: it_config_free() + * + * Free any resources associated with the it_config_t structure. + * + * Parameters: + * cfg A C representation of the current iSCSI configuration + */ +void +it_config_free(it_config_t *cfg); + +/* + * Function: it_tgt_create() + * + * Allocate and create an it_tgt_t structure representing a new iSCSI + * target node. If tgt_name is NULL, then a unique target node name will + * be generated automatically. Otherwise, the value of tgt_name will be + * used as the target node name. The new it_tgt_t structure is added to + * the target list (cfg_tgt_list) in the configuration structure, and the + * new target will not be instantiated until the modified configuration + * is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tgt Pointer to an iSCSI target structure + * tgt_name The target node name for the target to be created. + * The name must be in either IQN or EUI format. If + * this value is NULL, a node name will be generated + * automatically in IQN format. + * + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid parameter + * EEXIST The requested target node name is already configured + * EFAULT Invalid iSCSI target name + */ +int +it_tgt_create(it_config_t *cfg, it_tgt_t **tgt, char *tgt_name); + +/* + * Function: it_tgt_setprop() + * + * Validate the provided property list and set the properties for + * the specified target. If errlist is not NULL, returns detailed + * errors for each property that failed. The format for errorlist + * is key = property, value = error string. + * + * Parameters: + * + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tgt Pointer to an iSCSI target structure + * proplist nvlist_t containing properties for this target. + * errlist (optional) nvlist_t of errors encountered when + * validating the properties. + * + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid property + * + */ +int +it_tgt_setprop(it_config_t *cfg, it_tgt_t *tgt, nvlist_t *proplist, + nvlist_t **errlist); + + +/* + * Function: it_tgt_delete() + * + * Delete target represented by 'tgt', where 'tgt' is an existing + * it_tgt_t structure within the configuration 'cfg'. The target removal + * will not take effect until the modified configuration is committed + * by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tgt Pointer to an iSCSI target structure + * force Set the target to offline before removing it from + * the config. If not specified, the operation will + * fail if the target is determined to be online. + * + * Return Values: + * 0 Success + * EBUSY Target is online + */ +int +it_tgt_delete(it_config_t *cfg, it_tgt_t *tgt, boolean_t force); + +/* + * Function: it_tpgt_create() + * + * Allocate and create an it_tpgt_t structure representing a new iSCSI + * target portal group tag. The new it_tpgt_t structure is added to the + * target tpgt list (tgt_tpgt_list) in the it_tgt_t structure. The new + * target portal group tag will not be instantiated until the modified + * configuration is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tgt Pointer to the iSCSI target structure associated + * with the target portal group tag + * tpgt Pointer to a target portal group tag structure + * tpg_name The name of the TPG to be associated with this TPGT + * tpgt_tag 16-bit numerical identifier for this TPGT. Valid + * values are 2 through 65535. If tpgt_tag is '0', + * this function will assign an appropriate tag number. + * If tpgt_tag is != 0, and the requested number is + * unavailable, another value will be chosen. + * + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid parameter + * EEXIST Specified TPG is already associated with the target + * E2BIG All tag numbers already in use + */ +int +it_tpgt_create(it_config_t *cfg, it_tgt_t *tgt, it_tpgt_t **tpgt, + char *tpg_name, uint16_t tpgt_tag); + +/* + * Function: it_tpgt_delete() + * + * Delete the target portal group tag represented by 'tpgt', where + * 'tpgt' is an existing is_tpgt_t structure within the target 'tgt'. + * The target portal group tag removal will not take effect until the + * modified configuation is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tgt Pointer to the iSCSI target structure associated + * with the target portal group tag + * tpgt Pointer to a target portal group tag structure + */ +void +it_tpgt_delete(it_config_t *cfg, it_tgt_t *tgt, it_tpgt_t *tpgt); + +/* + * Function: it_tpg_create() + * + * Allocate and create an it_tpg_t structure representing a new iSCSI + * target portal group. The new it_tpg_t structure is added to the global + * tpg list (cfg_tgt_list) in the it_config_t structure. The new target + * portal group will not be instantiated until the modified configuration + * is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tpg Pointer to the it_tpg_t structure representing + * the target portal group + * tpg_name Identifier for the target portal group + * portal_ip_port A string containing an appropriatedly formatted + * IP address:port. Both IPv4 and IPv6 addresses are + * permitted. This value becomes the first portal in + * the TPG -- applications can add additional values + * using it_portal_create() before committing the TPG. + * Return Values: + * 0 Success + * ENOMEM Cannot allocate resources + * EINVAL Invalid parameter + * EEXIST Portal already configured for another portal group + * associated with this target. + */ +int +it_tpg_create(it_config_t *cfg, it_tpg_t **tpg, char *tpg_name, + char *portal_ip_port); + +/* + * Function: it_tpg_delete() + * + * Delete target portal group represented by 'tpg', where 'tpg' is an + * existing it_tpg_t structure within the global configuration 'cfg'. + * The target portal group removal will not take effect until the + * modified configuration is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configuration obtained from + * it_config_load() + * tpg Pointer to the it_tpg_t structure representing + * the target portal group + * force Remove this target portal group even if it's + * associated with one or more targets. + * + * Return Values: + * 0 Success + * EINVAL Invalid parameter + * EBUSY Portal group associated with one or more targets. + */ +int +it_tpg_delete(it_config_t *cfg, it_tpg_t *tpg, boolean_t force); + +/* + * Function: it_portal_create() + * + * Add an it_portal_t structure representing a new portal to the specified + * target portal group. The change to the target portal group will not take + * effect until the modified configuration is committed by calling + * it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configration obtained from + * it_config_load() + * tpg Pointer to the it_tpg_t structure representing the + * target portal group or "none" to remove + * portal Pointer to the it_portal_t structure representing + * the portal + * portal_ip_port A string containing an appropriately formatted + * IP address or IP address:port in either IPv4 or + * IPv6 format. + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid parameter + * EEXIST Portal already configured for another portal group + */ +int +it_portal_create(it_config_t *cfg, it_tpg_t *tpg, it_portal_t **portal, + char *portal_ip_port); + +/* + * Function: it_portal_delete() + * + * Remove the specified portal from the specified target portal group. + * The portal removal will not take effect until the modified configuration + * is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configration obtained from + * it_config_load() + * tpg Pointer to the it_tpg_t structure representing the + * target portal group + * portal Pointer to the it_portal_t structure representing + * the portal + */ +void +it_portal_delete(it_config_t *cfg, it_tpg_t *tpg, it_portal_t *portal); + +/* + * Function: it_ini_create() + * + * Add an initiator context to the global configuration. The new + * initiator context will not be instantiated until the modified + * configuration is committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configration obtained from + * it_config_load() + * ini Pointer to the it_ini_t structure representing + * the initiator context. + * ini_node_name The iSCSI node name of the remote initiator. + * + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid parameter. + * EEXIST Initiator already configured + * EFAULT Invalid initiator name + */ +int +it_ini_create(it_config_t *cfg, it_ini_t **ini, char *ini_node_name); + +/* + * Function: it_ini_setprop() + * + * Validate the provided property list and set the initiator properties. + * If errlist is not NULL, returns detailed errors for each property + * that failed. The format for errorlist is + * key = property, value = error string. + * + * Parameters: + * + * ini The initiator being updated. + * proplist nvlist_t containing properties for this target. + * errlist (optional) nvlist_t of errors encountered when + * validating the properties. + * + * Return Values: + * 0 Success + * ENOMEM Could not allocate resources + * EINVAL Invalid property + * + */ +int +it_ini_setprop(it_ini_t *ini, nvlist_t *proplist, nvlist_t **errlist); + +/* + * Function: it_ini_delete() + * + * Remove the specified initiator context from the global configuration. + * The removal will not take effect until the modified configuration is + * committed by calling it_config_commit(). + * + * Parameters: + * cfg The current iSCSI configration obtained from + * it_config_load() + * ini Pointer to the it_ini_t structure representing + * the initiator context. + */ +void +it_ini_delete(it_config_t *cfg, it_ini_t *ini); + +/* + * Function: it_config_free() + * + * Free any resources associated with the it_config_t structure. + * + * Parameters: + * cfg A C representation of the current iSCSI configuration + */ +void +it_config_free(it_config_t *cfg); + +/* + * Function: it_tgt_free() + * + * Frees an it_tgt_t structure. If tgt_next is not NULL, frees + * all structures in the list. + */ +void +it_tgt_free(it_tgt_t *tgt); + +/* + * Function: it_tpgt_free() + * + * Deallocates resources of an it_tpgt_t structure. If tpgt->next + * is not NULL, frees all members of the list. + */ +void +it_tpgt_free(it_tpgt_t *tpgt); + +/* + * Function: it_tpg_free() + * + * Deallocates resources associated with an it_tpg_t structure. + * If tpg->next is not NULL, frees all members of the list. + */ +void +it_tpg_free(it_tpg_t *tpg); + +/* + * Function: it_ini_free() + * + * Deallocates resources of an it_ini_t structure. If ini->next is + * not NULL, frees all members of the list. + */ +void +it_ini_free(it_ini_t *ini); + +/* + * Function: validate_iscsi_name() + * + * Ensures the passed-in string is a valid IQN or EUI iSCSI name + */ +boolean_t +validate_iscsi_name(char *in_name); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBISCSIT_H */ diff --git a/usr/src/lib/libiscsit/common/llib-liscsit b/usr/src/lib/libiscsit/common/llib-liscsit new file mode 100644 index 000000000000..8d9fc3360494 --- /dev/null +++ b/usr/src/lib/libiscsit/common/llib-liscsit @@ -0,0 +1,30 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/*LINTLIBRARY*/ +/*PROTOLIB1*/ + +#include +#include diff --git a/usr/src/lib/libiscsit/common/mapfile-vers b/usr/src/lib/libiscsit/common/mapfile-vers new file mode 100644 index 000000000000..cceac0978d56 --- /dev/null +++ b/usr/src/lib/libiscsit/common/mapfile-vers @@ -0,0 +1,79 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +SUNW_1.1 { + global: + it_config_load; + it_config_commit; + it_config_setprop; + it_config_free; + it_tgt_create; + it_tgt_setprop; + it_tgt_delete; + it_tgt_free; + it_tpg_create; + it_tpg_delete; + it_tpg_free; + it_ini_create; + it_ini_delete; + it_ini_setprop; + it_ini_free; + it_tpgt_create; + it_tpgt_delete; + it_tpgt_free; + it_portal_create; +}; + +SUNWprivate { + global: + it_config_to_nv; + it_nv_to_config; + it_nv_to_tgtlist; + it_tgtlist_to_nv; + it_tgt_to_nv; + it_nv_to_tgt; + it_tpgt_to_nv; + it_nv_to_tpgt; + it_tpgtlist_to_nv; + it_nv_to_tpgtlist; + it_tpg_to_nv; + it_nv_to_tpg; + it_tpglist_to_nv; + it_nv_to_tpglist; + it_ini_to_nv; + it_nv_to_ini; + it_inilist_to_nv; + it_nv_to_inilist; + it_common_convert_sa; + it_config_free_cmn; + it_tgt_free_cmn; + it_tpg_free_cmn; + it_ini_free_cmn; + it_tpgt_free_cmn; + sockaddr_to_str; + validate_iscsi_name; + local: + *; +}; + diff --git a/usr/src/lib/libiscsit/i386/Makefile b/usr/src/lib/libiscsit/i386/Makefile new file mode 100644 index 000000000000..95a52ca13f72 --- /dev/null +++ b/usr/src/lib/libiscsit/i386/Makefile @@ -0,0 +1,31 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +include ../Makefile.com + +all: $(LIBS) + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/libiscsit/sparc/Makefile b/usr/src/lib/libiscsit/sparc/Makefile new file mode 100644 index 000000000000..ef68abede63a --- /dev/null +++ b/usr/src/lib/libiscsit/sparc/Makefile @@ -0,0 +1,29 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +include ../Makefile.com + +all: $(LIBS) + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/libiscsit/sparcv9/Makefile b/usr/src/lib/libiscsit/sparcv9/Makefile new file mode 100644 index 000000000000..0dba37b85a30 --- /dev/null +++ b/usr/src/lib/libiscsit/sparcv9/Makefile @@ -0,0 +1,32 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +include ../Makefile.com +include ../../Makefile.lib.64 + +all: $(LIBS) + +install: all $(ROOTLIBS64) $(ROOTLINKS64) diff --git a/usr/src/pkgdefs/Makefile b/usr/src/pkgdefs/Makefile index d12b18d8b880..d68ca984a109 100644 --- a/usr/src/pkgdefs/Makefile +++ b/usr/src/pkgdefs/Makefile @@ -281,9 +281,13 @@ COMMON_SUBDIRS= \ SUNWippcore \ SUNWipplr \ SUNWipplu \ + SUNWiscsidmr \ + SUNWiscsidmu \ SUNWiscsir \ SUNWiscsitgtr \ SUNWiscsitgtu \ + SUNWiscsitr \ + SUNWiscsitu \ SUNWiscsiu \ SUNWisns \ SUNWisnsadm \ diff --git a/usr/src/pkgdefs/SUNWiscsidmr/Makefile b/usr/src/pkgdefs/SUNWiscsidmr/Makefile new file mode 100644 index 000000000000..4554f5a618f2 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsidmr/Makefile @@ -0,0 +1,37 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +DATAFILES += depend + +.KEEP_STATE: + +all: $(FILES) depend + +install: all pkg + +include ../Makefile.targ + diff --git a/usr/src/pkgdefs/SUNWiscsidmr/pkginfo.tmpl b/usr/src/pkgdefs/SUNWiscsidmr/pkginfo.tmpl new file mode 100644 index 000000000000..5bfb88a1bf38 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsidmr/pkginfo.tmpl @@ -0,0 +1,49 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# +# This required package information file describes characteristics of the +# package, such as package abbreviation, full package name, package version, +# and package architecture. +# +PKG="SUNWiscsidmr" +NAME="Sun iSCSI Data Mover (Root)" +ARCH="ISA" +CATEGORY="system" +BASEDIR=/ +SUNW_PKGVERS="1.0" +SUNW_PKGTYPE="root" +CLASSES="none" +DESC="Sun iSCSI Data Mover (Root)" +SUNW_PRODNAME="SunOS" +SUNW_PRODVERS="RELEASE/VERSION" +VERSION="ONVERS,REV=0.0.0" +VENDOR="Sun Microsystems, Inc." +HOTLINE="Please contact your local service provider" +EMAIL="" +MAXINST="1000" +SUNW_PKG_ALLZONES="true" +SUNW_PKG_HOLLOW="true" +SUNW_PKG_THISZONE="false" diff --git a/usr/src/pkgdefs/SUNWiscsidmr/prototype_com b/usr/src/pkgdefs/SUNWiscsidmr/prototype_com new file mode 100644 index 000000000000..8d166ddfbef6 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsidmr/prototype_com @@ -0,0 +1,46 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search # where to find pkg objects +#!include # include another 'prototype' file +#!default # default used if not specified on entry +#!= # puts parameter in pkg environment + +# +i copyright +i pkginfo +i depend +# +# SUNWiscsidmr files +# +d none kernel 0755 root sys +d none kernel/misc 0755 root sys +d none kernel/kmdb 0755 root sys diff --git a/usr/src/pkgdefs/SUNWiscsidmr/prototype_i386 b/usr/src/pkgdefs/SUNWiscsidmr/prototype_i386 new file mode 100644 index 000000000000..2f00ec513f0e --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsidmr/prototype_i386 @@ -0,0 +1,54 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search # where to find pkg objects +#!include # include another 'prototype' file +#!default # default used if not specified on entry +#!= # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# List files which are Intel specific here +# +# source locations relative to the prototype file +# +# +# SUNWiscsidmr +# +f none kernel/misc/idm 0755 root sys +d none kernel/misc/amd64 0755 root sys +f none kernel/misc/amd64/idm 0755 root sys +f none kernel/kmdb/idm 0555 root sys +d none kernel/kmdb/amd64 0755 root sys +f none kernel/kmdb/amd64/idm 0555 root sys diff --git a/usr/src/pkgdefs/SUNWiscsidmr/prototype_sparc b/usr/src/pkgdefs/SUNWiscsidmr/prototype_sparc new file mode 100644 index 000000000000..129c19c745b6 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsidmr/prototype_sparc @@ -0,0 +1,51 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search # where to find pkg objects +#!include # include another 'prototype' file +#!default # default used if not specified on entry +#!= # puts parameter in pkg environment +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# List files which are SPARC specific here +# +# source locations relative to the prototype file +# +# +# SUNWiscsidmr +# +d none kernel/misc/sparcv9 0755 root sys +f none kernel/misc/sparcv9/idm 0755 root sys +d none kernel/kmdb/sparcv9 0755 root sys +f none kernel/kmdb/sparcv9/idm 0555 root sys diff --git a/usr/src/pkgdefs/SUNWiscsidmu/Makefile b/usr/src/pkgdefs/SUNWiscsidmu/Makefile new file mode 100644 index 000000000000..a5e2160367fe --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsidmu/Makefile @@ -0,0 +1,35 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +.KEEP_STATE: + +all: $(FILES) depend + +install: all pkg + +include ../Makefile.targ + diff --git a/usr/src/pkgdefs/SUNWiscsidmu/depend b/usr/src/pkgdefs/SUNWiscsidmu/depend new file mode 100644 index 000000000000..995405be04ce --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsidmu/depend @@ -0,0 +1,50 @@ +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# This package information file defines software dependencies associated +# with the pkg. You can define three types of pkg dependencies with this file: +# P indicates a prerequisite for installation +# I indicates an incompatible package +# R indicates a reverse dependency +# see pkginfo(4), PKG parameter +# see pkginfo(4), NAME parameter +# see pkginfo(4), VERSION parameter +# see pkginfo(4), ARCH parameter +# +# () +# () +# ... +# +# ... +# + +P SUNWcar Core Architecture, (Root) +P SUNWcakr Core Solaris Kernel Architecture (Root) +P SUNWkvm Core Architecture, (Kvm) +P SUNWcsr Core Solaris, (Root) +P SUNWckr Core Solaris Kernel (Root) +P SUNWcnetr Core Solaris Network Infrastructure (Root) +P SUNWcsu Core Solaris, (Usr) +P SUNWcsd Core Solaris Devices +P SUNWcsl Core Solaris Libraries +P SUNWiscsidmr Sun iSCSI Data Mover (Root) diff --git a/usr/src/pkgdefs/SUNWiscsidmu/pkginfo.tmpl b/usr/src/pkgdefs/SUNWiscsidmu/pkginfo.tmpl new file mode 100644 index 000000000000..02dc05bc0a1e --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsidmu/pkginfo.tmpl @@ -0,0 +1,49 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# +# This required package information file describes characteristics of the +# package, such as package abbreviation, full package name, package version, +# and package architecture. +# +PKG="SUNWiscsidmu" +NAME="Sun iSCSI Data Mover (Usr)" +ARCH="ISA" +CATEGORY="system" +BASEDIR=/ +SUNW_PKGVERS="1.0" +SUNW_PKGTYPE="usr" +CLASSES="none" +DESC="Sun iSCSI Data Mover (Usr)" +SUNW_PRODNAME="SunOS" +SUNW_PRODVERS="RELEASE/VERSION" +VERSION="ONVERS,REV=0.0.0" +VENDOR="Sun Microsystems, Inc." +HOTLINE="Please contact your local service provider" +EMAIL="" +MAXINST="1000" +SUNW_PKG_ALLZONES="true" +SUNW_PKG_HOLLOW="true" +SUNW_PKG_THISZONE="false" diff --git a/usr/src/pkgdefs/SUNWiscsidmu/prototype_com b/usr/src/pkgdefs/SUNWiscsidmu/prototype_com new file mode 100644 index 000000000000..8acc9ea7a130 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsidmu/prototype_com @@ -0,0 +1,47 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search # where to find pkg objects +#!include # include another 'prototype' file +#!default # default used if not specified on entry +#!= # puts parameter in pkg environment + +# +i copyright +i pkginfo +i depend +# +# SUNWiscsidmu files +# +d none usr 0755 root sys +d none usr/lib 0755 root bin +d none usr/lib/mdb 0755 root sys +d none usr/lib/mdb/kvm 0755 root sys diff --git a/usr/src/pkgdefs/SUNWiscsidmu/prototype_i386 b/usr/src/pkgdefs/SUNWiscsidmu/prototype_i386 new file mode 100644 index 000000000000..0cc0e0db7955 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsidmu/prototype_i386 @@ -0,0 +1,51 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search # where to find pkg objects +#!include # include another 'prototype' file +#!default # default used if not specified on entry +#!= # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# List files which are Intel specific here +# +# source locations relative to the prototype file +# +# +# SUNWiscsidmu +# +f none usr/lib/mdb/kvm/idm.so 0555 root sys +d none usr/lib/mdb/kvm/amd64 0755 root sys +f none usr/lib/mdb/kvm/amd64/idm.so 0555 root sys diff --git a/usr/src/pkgdefs/SUNWiscsidmu/prototype_sparc b/usr/src/pkgdefs/SUNWiscsidmu/prototype_sparc new file mode 100644 index 000000000000..cbaa50c0954c --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsidmu/prototype_sparc @@ -0,0 +1,49 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search # where to find pkg objects +#!include # include another 'prototype' file +#!default # default used if not specified on entry +#!= # puts parameter in pkg environment +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# List files which are SPARC specific here +# +# source locations relative to the prototype file +# +# +# SUNWiscsidmu +# +d none usr/lib/mdb/kvm/sparcv9 0755 root sys +f none usr/lib/mdb/kvm/sparcv9/idm.so 0555 root sys diff --git a/usr/src/pkgdefs/SUNWiscsitr/Makefile b/usr/src/pkgdefs/SUNWiscsitr/Makefile new file mode 100644 index 000000000000..64a4ce0f7a54 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitr/Makefile @@ -0,0 +1,37 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +DATAFILES += i.manifest r.manifest + +.KEEP_STATE: + +all: $(FILES) depend i.manifest r.manifest + +install: all pkg + +include ../Makefile.targ + diff --git a/usr/src/pkgdefs/SUNWiscsitr/depend b/usr/src/pkgdefs/SUNWiscsitr/depend new file mode 100644 index 000000000000..719947f50ebf --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitr/depend @@ -0,0 +1,50 @@ +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This package information file defines software dependencies associated +# with the pkg. You can define three types of pkg dependencies with this file: +# P indicates a prerequisite for installation +# I indicates an incompatible package +# R indicates a reverse dependency +# see pkginfo(4), PKG parameter +# see pkginfo(4), NAME parameter +# see pkginfo(4), VERSION parameter +# see pkginfo(4), ARCH parameter +# +# () +# () +# ... +# +# ... +# + +P SUNWcar Core Architecture, (Root) +P SUNWcakr Core Solaris Kernel Architecture (Root) +P SUNWkvm Core Architecture, (Kvm) +P SUNWcsr Core Solaris, (Root) +P SUNWckr Core Solaris Kernel (Root) +P SUNWcnetr Core Solaris Network Infrastructure (Root) +P SUNWcsu Core Solaris, (Usr) +P SUNWcsd Core Solaris Devices +P SUNWcsl Core Solaris Libraries +P SUNWiscsidmr Sun iSCSI Data Mover (Root) +P SUNWiscsidmu Sun iSCSI Data Mover (Usr) diff --git a/usr/src/pkgdefs/SUNWiscsitr/pkginfo.tmpl b/usr/src/pkgdefs/SUNWiscsitr/pkginfo.tmpl new file mode 100644 index 000000000000..6e9df5bd41fc --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitr/pkginfo.tmpl @@ -0,0 +1,48 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This required package information file describes characteristics of the +# package, such as package abbreviation, full package name, package version, +# and package architecture. +# +PKG="SUNWiscsitr" +NAME="Sun iSCSI COMSTAR Port Provider (root)" +ARCH="ISA" +CATEGORY="system" +BASEDIR=/ +SUNW_PKGVERS="1.0" +SUNW_PKGTYPE="root" +CLASSES="manifest none" +DESC="Sun iSCSI COMSTAR Port Provider" +SUNW_PRODNAME="SunOS" +SUNW_PRODVERS="RELEASE/VERSION" +VERSION="ONVERS,REV=0.0.0" +VENDOR="Sun Microsystems, Inc." +HOTLINE="Please contact your local service provider" +EMAIL="" +MAXINST="1000" +SUNW_PKG_ALLZONES="true" +SUNW_PKG_HOLLOW="true" +SUNW_PKG_THISZONE="false" diff --git a/usr/src/pkgdefs/SUNWiscsitr/postinstall b/usr/src/pkgdefs/SUNWiscsitr/postinstall new file mode 100644 index 000000000000..c3e5b6753da5 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitr/postinstall @@ -0,0 +1,40 @@ +#!/bin/sh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +PATH="/usr/bin:/usr/sbin:${PATH}"; export PATH + +# Driver definitions +DRVR_NAME=iscsit; export DRVR_NAME +DRVR_PERM='* 0600 root sys'; export DRVR_PERM + +if [ "${BASEDIR}" = "/" ]; then + add_drv -m "${DRVR_PERM}" ${DRVR_NAME} +else + add_drv -b "${BASEDIR}" -m "${DRVR_PERM}" ${DRVR_NAME} +fi + +exit 0 diff --git a/usr/src/pkgdefs/SUNWiscsitr/preremove b/usr/src/pkgdefs/SUNWiscsitr/preremove new file mode 100644 index 000000000000..b8315bb5b650 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitr/preremove @@ -0,0 +1,36 @@ +#!/bin/sh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +PATH="/usr/bin:/usr/sbin:${PATH}" +export PATH + +DRVR_NAME=iscsit + +# Remove the driver entries but leave it attached. +/usr/sbin/rem_drv -b ${BASEDIR} ${DRVR_NAME} + +exit 0 diff --git a/usr/src/pkgdefs/SUNWiscsitr/prototype_com b/usr/src/pkgdefs/SUNWiscsitr/prototype_com new file mode 100644 index 000000000000..ebb06bdc9e2b --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitr/prototype_com @@ -0,0 +1,60 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search # where to find pkg objects +#!include # include another 'prototype' file +#!default # default used if not specified on entry +#!= # puts parameter in pkg environment + +# +# +i copyright +i pkginfo +i depend +i postinstall +i preremove +i i.manifest +i r.manifest +# +# SUNWiscsitr files +# +d none kernel 0755 root sys +d none kernel/drv 0755 root sys +f none kernel/drv/iscsit.conf 0644 root sys +d none var 755 root sys +d none var/svc 755 root sys +d none var/svc/manifest 755 root sys +d none var/svc/manifest/network 755 root sys +d none var/svc/manifest/network/iscsi 755 root sys +f manifest var/svc/manifest/network/iscsi/iscsi-target.xml 0444 root sys +d none lib 755 root bin +d none lib/svc 755 root bin +d none lib/svc/method 755 root bin +f none lib/svc/method/iscsi-target 0555 root bin diff --git a/usr/src/pkgdefs/SUNWiscsitr/prototype_i386 b/usr/src/pkgdefs/SUNWiscsitr/prototype_i386 new file mode 100644 index 000000000000..c0891f36a2b1 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitr/prototype_i386 @@ -0,0 +1,51 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search # where to find pkg objects +#!include # include another 'prototype' file +#!default # default used if not specified on entry +#!= # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# List files which are Intel specific here +# +# source locations relative to the prototype file +# +# +# SUNWiscsitr +# +f none kernel/drv/iscsit 0755 root sys +d none kernel/drv/amd64 0755 root sys +f none kernel/drv/amd64/iscsit 0755 root sys diff --git a/usr/src/pkgdefs/SUNWiscsitr/prototype_sparc b/usr/src/pkgdefs/SUNWiscsitr/prototype_sparc new file mode 100644 index 000000000000..f626a7da5aed --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitr/prototype_sparc @@ -0,0 +1,49 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search # where to find pkg objects +#!include # include another 'prototype' file +#!default # default used if not specified on entry +#!= # puts parameter in pkg environment +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# List files which are SPARC specific here +# +# source locations relative to the prototype file +# +# +# SUNWiscsitr +# +d none kernel/drv/sparcv9 0755 root sys +f none kernel/drv/sparcv9/iscsit 0755 root sys diff --git a/usr/src/pkgdefs/SUNWiscsitu/Makefile b/usr/src/pkgdefs/SUNWiscsitu/Makefile new file mode 100644 index 000000000000..a5e2160367fe --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitu/Makefile @@ -0,0 +1,35 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +.KEEP_STATE: + +all: $(FILES) depend + +install: all pkg + +include ../Makefile.targ + diff --git a/usr/src/pkgdefs/SUNWiscsitu/depend b/usr/src/pkgdefs/SUNWiscsitu/depend new file mode 100644 index 000000000000..fd563fed3280 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitu/depend @@ -0,0 +1,49 @@ +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This package information file defines software dependencies associated +# with the pkg. You can define three types of pkg dependencies with this file: +# P indicates a prerequisite for installation +# I indicates an incompatible package +# R indicates a reverse dependency +# see pkginfo(4), PKG parameter +# see pkginfo(4), NAME parameter +# see pkginfo(4), VERSION parameter +# see pkginfo(4), ARCH parameter +# +# () +# () +# ... +# +# ... +# + +P SUNWcar Core Architecture, (Root) +P SUNWcakr Core Solaris Kernel Architecture (Root) +P SUNWkvm Core Architecture, (Kvm) +P SUNWcsr Core Solaris, (Root) +P SUNWckr Core Solaris Kernel (Root) +P SUNWcnetr Core Solaris Network Infrastructure (Root) +P SUNWcsu Core Solaris, (Usr) +P SUNWcsd Core Solaris Devices +P SUNWcsl Core Solaris Libraries +P SUNWiscsitr Sun iSCSI COMSTAR port provider (Root) diff --git a/usr/src/pkgdefs/SUNWiscsitu/pkginfo.tmpl b/usr/src/pkgdefs/SUNWiscsitu/pkginfo.tmpl new file mode 100644 index 000000000000..bb9f962ae2d9 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitu/pkginfo.tmpl @@ -0,0 +1,48 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This required package information file describes characteristics of the +# package, such as package abbreviation, full package name, package version, +# and package architecture. +# +PKG="SUNWiscsitu" +NAME="Sun iSCSI COMSTAR Port Provider" +ARCH="ISA" +CATEGORY="system" +BASEDIR=/ +SUNW_PKGVERS="1.0" +SUNW_PKGTYPE="usr" +CLASSES="none" +DESC="Sun iSCSI COMSTAR Port Provider" +SUNW_PRODNAME="SunOS" +SUNW_PRODVERS="RELEASE/VERSION" +VERSION="ONVERS,REV=0.0.0" +VENDOR="Sun Microsystems, Inc." +HOTLINE="Please contact your local service provider" +EMAIL="" +MAXINST="1000" +SUNW_PKG_ALLZONES="true" +SUNW_PKG_HOLLOW="false" +SUNW_PKG_THISZONE="false" diff --git a/usr/src/pkgdefs/SUNWiscsitu/prototype_com b/usr/src/pkgdefs/SUNWiscsitu/prototype_com new file mode 100644 index 000000000000..41d30d931b2c --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitu/prototype_com @@ -0,0 +1,54 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search # where to find pkg objects +#!include # include another 'prototype' file +#!default # default used if not specified on entry +#!= # puts parameter in pkg environment + +i copyright +i pkginfo +i depend +# +# SUNWiscsitu files +# +d none usr 0755 root sys +d none usr/lib 0755 root bin +f none usr/lib/libiscsit.so.1 0755 root bin +s none usr/lib/libiscsit.so=libiscsit.so.1 +f none usr/lib/llib-liscsit 644 root bin +f none usr/lib/llib-liscsit.ln 644 root bin +d none usr/sbin 0755 root bin +f none usr/sbin/itadm 0555 root bin +d none usr/include 0755 root bin +f none usr/include/libiscsit.h 644 root bin +d none usr/include/sys 0755 root bin +d none usr/include/sys/iscsit 0755 root bin +f none usr/include/sys/iscsit/iscsit_common.h 644 root bin diff --git a/usr/src/pkgdefs/SUNWiscsitu/prototype_i386 b/usr/src/pkgdefs/SUNWiscsitu/prototype_i386 new file mode 100644 index 000000000000..431563a7bb82 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitu/prototype_i386 @@ -0,0 +1,51 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search # where to find pkg objects +#!include # include another 'prototype' file +#!default # default used if not specified on entry +#!= # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# List files which are Intel specific here +# +# source locations relative to the prototype file +# +# SUNWiscsitu +# +d none usr/lib/amd64 0755 root bin +f none usr/lib/amd64/libiscsit.so.1 0755 root bin +s none usr/lib/amd64/libiscsit.so=libiscsit.so.1 +f none usr/lib/amd64/llib-liscsit.ln 644 root bin diff --git a/usr/src/pkgdefs/SUNWiscsitu/prototype_sparc b/usr/src/pkgdefs/SUNWiscsitu/prototype_sparc new file mode 100644 index 000000000000..8f63eb24e5d8 --- /dev/null +++ b/usr/src/pkgdefs/SUNWiscsitu/prototype_sparc @@ -0,0 +1,51 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search # where to find pkg objects +#!include # include another 'prototype' file +#!default # default used if not specified on entry +#!= # puts parameter in pkg environment +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# +# List files which are SPARC specific here +# +# source locations relative to the prototype file +# +# +# SUNWiscsitu +# +d none usr/lib/sparcv9 0755 root bin +f none usr/lib/sparcv9/libiscsit.so.1 0755 root bin +s none usr/lib/sparcv9/libiscsit.so=libiscsit.so.1 +f none usr/lib/sparcv9/llib-liscsit.ln 644 root bin diff --git a/usr/src/pkgdefs/etc/exception_list_i386 b/usr/src/pkgdefs/etc/exception_list_i386 index 2d2a0a5f37d4..a4320e8aacd4 100644 --- a/usr/src/pkgdefs/etc/exception_list_i386 +++ b/usr/src/pkgdefs/etc/exception_list_i386 @@ -876,6 +876,21 @@ lib/llib-liscsitgt.ln i386 lib/amd64/libiscsitgt.so i386 lib/amd64/llib-liscsitgt.ln i386 # +# These files are used by the COMSTAR iSCSI target port provider +# +usr/include/sys/idm i386 +usr/include/sys/idm/idm.h i386 +usr/include/sys/idm/idm_conn_sm.h i386 +usr/include/sys/idm/idm_impl.h i386 +usr/include/sys/idm/idm_so.h i386 +usr/include/sys/idm/idm_text.h i386 +usr/include/sys/idm/idm_transport.h i386 +usr/include/sys/iscsit/chap.h i386 +usr/include/sys/iscsit/iscsi_if.h i386 +usr/include/sys/iscsit/isns_protocol.h i386 +usr/include/sys/iscsit/radius_packet.h i386 +usr/include/sys/iscsit/radius_protocol.h i386 +# # libshare is private and the 64-bit sharemgr is not delivered. # usr/lib/libshare.so i386 diff --git a/usr/src/pkgdefs/etc/exception_list_sparc b/usr/src/pkgdefs/etc/exception_list_sparc index 21bb574f2c8d..1144b94dcde6 100644 --- a/usr/src/pkgdefs/etc/exception_list_sparc +++ b/usr/src/pkgdefs/etc/exception_list_sparc @@ -952,6 +952,21 @@ lib/llib-liscsitgt.ln sparc lib/sparcv9/libiscsitgt.so sparc lib/sparcv9/llib-liscsitgt.ln sparc # +# These files are used by the COMSTAR iSCSI target port provider +# +usr/include/sys/idm sparc +usr/include/sys/idm/idm.h sparc +usr/include/sys/idm/idm_conn_sm.h sparc +usr/include/sys/idm/idm_impl.h sparc +usr/include/sys/idm/idm_so.h sparc +usr/include/sys/idm/idm_text.h sparc +usr/include/sys/idm/idm_transport.h sparc +usr/include/sys/iscsit/chap.h sparc +usr/include/sys/iscsit/iscsi_if.h sparc +usr/include/sys/iscsit/isns_protocol.h sparc +usr/include/sys/iscsit/radius_packet.h sparc +usr/include/sys/iscsit/radius_protocol.h sparc +# # libshare is private and the 64-bit sharemg is not delivered. # usr/lib/libshare.so sparc diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files index 87df03b0b419..fd16747a60f3 100644 --- a/usr/src/uts/common/Makefile.files +++ b/usr/src/uts/common/Makefile.files @@ -815,6 +815,14 @@ FCT_OBJS += discovery.o fct.o QLT_OBJS += 2400.o 2500.o qlt.o qlt_dma.o +ISCSIT_SHARED_OBJS += \ + iscsit_common.o + +ISCSIT_OBJS += $(ISCSIT_SHARED_OBJS) \ + iscsit.o iscsit_tgt.o iscsit_sess.o iscsit_login.o \ + iscsit_text.o iscsit_isns.o iscsit_radiusauth.o \ + iscsit_radiuspacket.o iscsit_auth.o iscsit_authclient.o + STMF_OBJS += lun_map.o stmf.o STMF_SBD_OBJS += filedisk.o memdisk.o sbd.o sbd_scsi.o @@ -1631,6 +1639,11 @@ DDA_OBJS += dda.o DMD_OBJS += dmd.o +IDM_SHARED_OBJS += base64.o + +IDM_OBJS += $(IDM_SHARED_OBJS) \ + idm.o idm_impl.o idm_text.o idm_conn_sm.o idm_so.o + # # Build up defines and paths. # diff --git a/usr/src/uts/common/Makefile.rules b/usr/src/uts/common/Makefile.rules index f131d6860860..2aa5f49e5ada 100644 --- a/usr/src/uts/common/Makefile.rules +++ b/usr/src/uts/common/Makefile.rules @@ -565,6 +565,14 @@ $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/comstar/port/qlt/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) +$(OBJS_DIR)/%.o: $(COMMONBASE)/iscsit/%.c + $(COMPILE.c) -o $@ $< + $(CTFCONVERT_O) + +$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/comstar/port/iscsit/%.c + $(COMPILE.c) -o $@ $< + $(CTFCONVERT_O) + $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/comstar/lu/stmf_sbd/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) @@ -633,6 +641,14 @@ $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/ib/ibtl/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) +$(OBJS_DIR)/%.o: $(COMMONBASE)/iscsi/%.c + $(COMPILE.c) -o $@ $< + $(CTFCONVERT_O) + +$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/idm/%.c + $(COMPILE.c) -o $@ $< + $(CTFCONVERT_O) + $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/ipw/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) @@ -1613,6 +1629,12 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/comstar/port/fct/%.c $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/comstar/port/qlt/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) +$(LINTS_DIR)/%.ln: $(COMMONBASE)/iscsit/%.c + @($(LHEAD) $(LINT.c) $< $(LTAIL)) + +$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/comstar/port/iscsit/%.c + @($(LHEAD) $(LINT.c) $< $(LTAIL)) + $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/comstar/stmf/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) @@ -1664,6 +1686,12 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/ib/ibnex/%.c $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/ib/ibtl/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) +$(LINTS_DIR)/%.ln: $(COMMONBASE)/iscsi/%.c + @($(LHEAD) $(LINT.c) $< $(LTAIL)) + +$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/idm/%.c + @($(LHEAD) $(LINT.c) $< $(LTAIL)) + $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/ipw/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.c new file mode 100644 index 000000000000..59412b159e5b --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.c @@ -0,0 +1,2480 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define ISCSIT_VERSION BUILD_DATE "-1.18dev" +#define ISCSIT_NAME_VERSION "COMSTAR ISCSIT v" ISCSIT_VERSION + +/* + * DDI entry points. + */ +static int iscsit_drv_attach(dev_info_t *, ddi_attach_cmd_t); +static int iscsit_drv_detach(dev_info_t *, ddi_detach_cmd_t); +static int iscsit_drv_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); +static int iscsit_drv_open(dev_t *, int, int, cred_t *); +static int iscsit_drv_close(dev_t, int, int, cred_t *); +static boolean_t iscsit_drv_busy(void); +static int iscsit_drv_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); +static boolean_t iscsit_cmdsn_in_window(iscsit_conn_t *ict, uint32_t cmdsn); +static void iscsit_send_direct_scsi_resp(iscsit_conn_t *ict, idm_pdu_t *rx_pdu, + uint8_t response, uint8_t cmd_status); +static void iscsit_send_task_mgmt_resp(idm_pdu_t *tm_resp_pdu, + uint8_t tm_status); + +extern struct mod_ops mod_miscops; + + +static struct cb_ops iscsit_cb_ops = { + iscsit_drv_open, /* cb_open */ + iscsit_drv_close, /* cb_close */ + nodev, /* cb_strategy */ + nodev, /* cb_print */ + nodev, /* cb_dump */ + nodev, /* cb_read */ + nodev, /* cb_write */ + iscsit_drv_ioctl, /* cb_ioctl */ + nodev, /* cb_devmap */ + nodev, /* cb_mmap */ + nodev, /* cb_segmap */ + nochpoll, /* cb_chpoll */ + ddi_prop_op, /* cb_prop_op */ + NULL, /* cb_streamtab */ + D_MP, /* cb_flag */ + CB_REV, /* cb_rev */ + nodev, /* cb_aread */ + nodev, /* cb_awrite */ +}; + +static struct dev_ops iscsit_dev_ops = { + DEVO_REV, /* devo_rev */ + 0, /* devo_refcnt */ + iscsit_drv_getinfo, /* devo_getinfo */ + nulldev, /* devo_identify */ + nulldev, /* devo_probe */ + iscsit_drv_attach, /* devo_attach */ + iscsit_drv_detach, /* devo_detach */ + nodev, /* devo_reset */ + &iscsit_cb_ops, /* devo_cb_ops */ + NULL, /* devo_bus_ops */ + NULL, /* devo_power */ +}; + +static struct modldrv modldrv = { + &mod_driverops, + "iSCSI Target", + &iscsit_dev_ops, +}; + +static struct modlinkage modlinkage = { + MODREV_1, + &modldrv, + NULL, +}; + + +iscsit_global_t iscsit_global; + +kmem_cache_t *iscsit_status_pdu_cache; + +boolean_t iscsit_sm_logging = B_FALSE; + +static idm_status_t iscsit_init(dev_info_t *dip); +static idm_status_t iscsit_enable_svc(iscsit_hostinfo_t *hostinfo); +static void iscsit_disable_svc(void); + +static void +iscsit_op_scsi_task_mgmt(iscsit_conn_t *ict, idm_pdu_t *rx_pdu); + +static void +iscsit_pdu_op_noop(iscsit_conn_t *ict, idm_pdu_t *rx_pdu); + +static void +iscsit_pdu_op_login_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu); + +void +iscsit_pdu_op_text_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu); + +static void +iscsit_pdu_op_logout_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu); + +int iscsit_cmd_window(); + +void +iscsit_set_cmdsn(iscsit_conn_t *ict, idm_pdu_t *rx_pdu); + +static void +iscsit_calc_rspsn(iscsit_conn_t *ict, idm_pdu_t *resp); + +static void +iscsit_deferred_dispatch(idm_pdu_t *rx_pdu); + +static void +iscsit_deferred(void *rx_pdu_void); + +static idm_status_t +iscsit_conn_accept(idm_conn_t *ic); + +static idm_status_t +iscsit_ffp_enabled(idm_conn_t *ic); + +static idm_status_t +iscsit_ffp_disabled(idm_conn_t *ic, idm_ffp_disable_t disable_class); + +static idm_status_t +iscsit_conn_lost(idm_conn_t *ic); + +static idm_status_t +iscsit_conn_destroy(idm_conn_t *ic); + +static stmf_data_buf_t * +iscsit_dbuf_alloc(scsi_task_t *task, uint32_t size, uint32_t *pminsize, + uint32_t flags); + +static void +iscsit_dbuf_free(stmf_dbuf_store_t *ds, stmf_data_buf_t *dbuf); + +static void +iscsit_buf_xfer_cb(idm_buf_t *idb, idm_status_t status); + +static void +iscsit_send_good_status_done(idm_pdu_t *pdu, idm_status_t status); + +static void +iscsit_send_status_done(idm_pdu_t *pdu, idm_status_t status); + +static stmf_status_t +iscsit_idm_to_stmf(idm_status_t idmrc); + +static iscsit_task_t * +iscsit_task_alloc(iscsit_conn_t *ict); + +static void +iscsit_task_free(iscsit_task_t *itask); + +static iscsit_task_t * +iscsit_tm_task_alloc(iscsit_conn_t *ict); + +static void +iscsit_tm_task_free(iscsit_task_t *itask); + +static int +iscsit_status_pdu_constructor(void *pdu_void, void *arg, int flags); + +static void +iscsit_pp_cb(struct stmf_port_provider *pp, int cmd, void *arg, uint32_t flags); + +static it_cfg_status_t +iscsit_config_merge(it_config_t *cfg); + +static idm_status_t +iscsit_login_fail(idm_conn_t *ic); + +int +_init(void) +{ + int rc; + + rw_init(&iscsit_global.global_rwlock, NULL, RW_DRIVER, NULL); + iscsit_global.global_svc_state = ISE_DETACHED; + + if ((rc = mod_install(&modlinkage)) != 0) { + rw_destroy(&iscsit_global.global_rwlock); + return (rc); + } + + return (rc); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +int +_fini(void) +{ + int rc; + + rc = mod_remove(&modlinkage); + + if (rc == 0) { + rw_destroy(&iscsit_global.global_rwlock); + } + + return (rc); +} + +/* + * DDI entry points. + */ + +/* ARGSUSED */ +static int +iscsit_drv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, + void **result) +{ + ulong_t instance = getminor((dev_t)arg); + + switch (cmd) { + case DDI_INFO_DEVT2DEVINFO: + *result = iscsit_global.global_dip; + return (DDI_SUCCESS); + + case DDI_INFO_DEVT2INSTANCE: + *result = (void *)instance; + return (DDI_SUCCESS); + + default: + break; + } + + return (DDI_FAILURE); +} + +static int +iscsit_drv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + if (cmd != DDI_ATTACH) { + return (DDI_FAILURE); + } + + if (ddi_get_instance(dip) != 0) { + /* we only allow instance 0 to attach */ + return (DDI_FAILURE); + } + + /* create the minor node */ + if (ddi_create_minor_node(dip, ISCSIT_MODNAME, S_IFCHR, 0, + DDI_PSEUDO, 0) != DDI_SUCCESS) { + cmn_err(CE_WARN, "iscsit_drv_attach: " + "failed creating minor node"); + return (DDI_FAILURE); + } + + if (iscsit_init(dip) != IDM_STATUS_SUCCESS) { + cmn_err(CE_WARN, "iscsit_drv_attach: " + "failed to initialize"); + ddi_remove_minor_node(dip, NULL); + return (DDI_FAILURE); + } + + iscsit_global.global_svc_state = ISE_DISABLED; + iscsit_global.global_dip = dip; + + return (DDI_SUCCESS); +} + +/*ARGSUSED*/ +static int +iscsit_drv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + if (cmd != DDI_DETACH) + return (DDI_FAILURE); + + ISCSIT_GLOBAL_LOCK(RW_WRITER); + if (iscsit_drv_busy()) { + ISCSIT_GLOBAL_UNLOCK(); + return (EBUSY); + } + + iscsit_global.global_dip = NULL; + ddi_remove_minor_node(dip, NULL); + + ldi_ident_release(iscsit_global.global_li); + iscsit_global.global_svc_state = ISE_DETACHED; + + ISCSIT_GLOBAL_UNLOCK(); + + return (DDI_SUCCESS); +} + +/*ARGSUSED*/ +static int +iscsit_drv_open(dev_t *devp, int flag, int otyp, cred_t *credp) +{ + return (0); +} + +/* ARGSUSED */ +static int +iscsit_drv_close(dev_t dev, int flag, int otyp, cred_t *credp) +{ + return (0); +} + +static boolean_t +iscsit_drv_busy(void) +{ + switch (iscsit_global.global_svc_state) { + case ISE_DISABLED: + case ISE_DETACHED: + return (B_FALSE); + default: + return (B_TRUE); + } + /* NOTREACHED */ +} + +/* ARGSUSED */ +static int +iscsit_drv_ioctl(dev_t drv, int cmd, intptr_t argp, int flag, cred_t *cred, + int *retval) +{ + iscsit_ioc_set_config_t setcfg; + iscsit_ioc_set_config32_t setcfg32; + /* iscsit_ioc_get_config_t getcfg; */ + char *cfg_pnvlist; + nvlist_t *cfg_nvlist; + it_config_t *cfg; + idm_status_t idmrc; + int rc = 0; + + if (drv_priv(cred) != 0) { + return (EPERM); + } + + ISCSIT_GLOBAL_LOCK(RW_WRITER); + + /* + * Validate ioctl requests against global service state + */ + switch (iscsit_global.global_svc_state) { + case ISE_ENABLED: + if (cmd == ISCSIT_IOC_DISABLE_SVC) { + iscsit_global.global_svc_state = ISE_DISABLING; + } else if (cmd == ISCSIT_IOC_ENABLE_SVC) { + /* Already enabled */ + ISCSIT_GLOBAL_UNLOCK(); + return (0); + } else { + iscsit_global.global_svc_state = ISE_BUSY; + } + break; + case ISE_DISABLED: + if (cmd == ISCSIT_IOC_ENABLE_SVC) { + iscsit_global.global_svc_state = ISE_ENABLING; + } else if (cmd == ISCSIT_IOC_DISABLE_SVC) { + /* Already disabled */ + ISCSIT_GLOBAL_UNLOCK(); + return (0); + } else { + rc = EFAULT; + } + break; + case ISE_ENABLING: + case ISE_DISABLING: + rc = EAGAIN; + break; + case ISE_DETACHED: + default: + rc = EFAULT; + break; + } + + ISCSIT_GLOBAL_UNLOCK(); + if (rc != 0) + return (rc); + + /* Handle ioctl request (enable/disable have already been handled) */ + switch (cmd) { + case ISCSIT_IOC_SET_CONFIG: + switch (ddi_model_convert_from(flag & FMODELS)) { + case DDI_MODEL_ILP32: + if (ddi_copyin((void *)argp, &setcfg32, + sizeof (iscsit_ioc_set_config32_t), flag) != 0) + return (EFAULT); + + setcfg.set_cfg_pnvlist = + (char *)((uintptr_t)setcfg32.set_cfg_pnvlist); + setcfg.set_cfg_vers = setcfg32.set_cfg_vers; + setcfg.set_cfg_pnvlist_len = + setcfg32.set_cfg_pnvlist_len; + break; + case DDI_MODEL_NONE: + if (ddi_copyin((void *)argp, &setcfg, + sizeof (iscsit_ioc_set_config_t), flag) != 0) + return (EFAULT); + break; + } + + /* Check API version */ + if (setcfg.set_cfg_vers != ISCSIT_API_VERS0) { + return (EINVAL); + } + + /* Config is in packed nvlist format so unpack it */ + cfg_pnvlist = kmem_alloc(setcfg.set_cfg_pnvlist_len, + KM_SLEEP); + ASSERT(cfg_pnvlist != NULL); + + if (ddi_copyin(setcfg.set_cfg_pnvlist, cfg_pnvlist, + setcfg.set_cfg_pnvlist_len, flag) != 0) { + kmem_free(cfg_pnvlist, setcfg.set_cfg_pnvlist_len); + return (EFAULT); + } + + if (nvlist_unpack(cfg_pnvlist, setcfg.set_cfg_pnvlist_len, + &cfg_nvlist, KM_SLEEP) != 0) { + kmem_free(cfg_pnvlist, setcfg.set_cfg_pnvlist_len); + return (EINVAL); + } + + /* Translate nvlist */ + if (it_nv_to_config(cfg_nvlist, &cfg) != 0) { + cmn_err(CE_WARN, "Configuration is invalid"); + kmem_free(cfg_pnvlist, setcfg.set_cfg_pnvlist_len); + nvlist_free(cfg_nvlist); + return (EINVAL); + } + + /* Update config */ + if (iscsit_config_merge(cfg) != 0) { + kmem_free(cfg_pnvlist, setcfg.set_cfg_pnvlist_len); + nvlist_free(cfg_nvlist); + return (EIO); + } + + it_config_free_cmn(cfg); + kmem_free(cfg_pnvlist, setcfg.set_cfg_pnvlist_len); + nvlist_free(cfg_nvlist); + + /* + * Now that the reconfig is complete set our state back to + * enabled. + */ + ISCSIT_GLOBAL_LOCK(RW_WRITER); + iscsit_global.global_svc_state = ISE_ENABLED; + ISCSIT_GLOBAL_UNLOCK(); + break; + case ISCSIT_IOC_ENABLE_SVC: { + iscsit_hostinfo_t hostinfo; + + if (ddi_copyin((void *)argp, &hostinfo.length, + sizeof (hostinfo.length), flag) != 0) { + iscsit_global.global_svc_state = ISE_DISABLED; + return (EFAULT); + } + + if (hostinfo.length > sizeof (hostinfo.fqhn)) + hostinfo.length = sizeof (hostinfo.fqhn); + + if (ddi_copyin((void *)((caddr_t)argp + + sizeof (hostinfo.length)), &hostinfo.fqhn, + hostinfo.length, flag) != 0) { + iscsit_global.global_svc_state = ISE_DISABLED; + return (EFAULT); + } + + idmrc = iscsit_enable_svc(&hostinfo); + ISCSIT_GLOBAL_LOCK(RW_WRITER); + if (idmrc == IDM_STATUS_SUCCESS) { + iscsit_global.global_svc_state = ISE_ENABLED; + } else { + rc = EIO; + iscsit_global.global_svc_state = ISE_DISABLED; + } + ISCSIT_GLOBAL_UNLOCK(); + break; + } + case ISCSIT_IOC_DISABLE_SVC: + iscsit_disable_svc(); + ISCSIT_GLOBAL_LOCK(RW_WRITER); + iscsit_global.global_svc_state = ISE_DISABLED; + ISCSIT_GLOBAL_UNLOCK(); + break; + default: + rc = EINVAL; + } + + /* Don't forget to clear ISE_BUSY state */ + ASSERT(iscsit_global.global_svc_state != ISE_BUSY); + + return (rc); +} + +static idm_status_t +iscsit_init(dev_info_t *dip) +{ + int rc; + + rc = ldi_ident_from_dip(dip, &iscsit_global.global_li); + ASSERT(rc == 0); /* Failure indicates invalid argument */ + + iscsit_global.global_svc_state = ISE_DISABLED; + + return (IDM_STATUS_SUCCESS); +} + +/* + * iscsit_enable_svc + * + * registers all the configured targets and target portals with STMF + */ +static idm_status_t +iscsit_enable_svc(iscsit_hostinfo_t *hostinfo) +{ + stmf_port_provider_t *pp; + stmf_dbuf_store_t *dbuf_store; + boolean_t did_iscsit_isns_init; + idm_status_t retval = IDM_STATUS_SUCCESS; + + ASSERT(iscsit_global.global_svc_state == ISE_ENABLING); + + /* + * Make sure that can tell if we have partially allocated + * in case we need to exit and tear down anything allocated. + */ + iscsit_global.global_tsih_pool = NULL; + iscsit_global.global_dbuf_store = NULL; + iscsit_status_pdu_cache = NULL; + pp = NULL; + iscsit_global.global_pp = NULL; + iscsit_global.global_default_tpg = NULL; + did_iscsit_isns_init = B_FALSE; + iscsit_global.global_dispatch_taskq = NULL; + + /* Setup remaining fields in iscsit_global_t */ + idm_refcnt_init(&iscsit_global.global_refcnt, + &iscsit_global); + + avl_create(&iscsit_global.global_discovery_sessions, + iscsit_sess_avl_compare, sizeof (iscsit_sess_t), + offsetof(iscsit_sess_t, ist_tgt_ln)); + + avl_create(&iscsit_global.global_target_list, + iscsit_tgt_avl_compare, sizeof (iscsit_tgt_t), + offsetof(iscsit_tgt_t, target_global_ln)); + + list_create(&iscsit_global.global_deleted_target_list, + sizeof (iscsit_tgt_t), + offsetof(iscsit_tgt_t, target_global_deleted_ln)); + + avl_create(&iscsit_global.global_tpg_list, + iscsit_tpg_avl_compare, sizeof (iscsit_tpg_t), + offsetof(iscsit_tpg_t, tpg_global_ln)); + + avl_create(&iscsit_global.global_ini_list, + iscsit_ini_avl_compare, sizeof (iscsit_ini_t), + offsetof(iscsit_ini_t, ini_global_ln)); + + iscsit_global.global_tsih_pool = vmem_create("iscsit_tsih_pool", + (void *)1, ISCSI_MAX_TSIH, 1, NULL, NULL, NULL, 0, + VM_SLEEP | VMC_IDENTIFIER); + + /* + * Setup STMF dbuf store. Our buffers are bound to a specific + * connection so we really can't let STMF cache buffers for us. + * Consequently we'll just allocate one global buffer store. + */ + dbuf_store = stmf_alloc(STMF_STRUCT_DBUF_STORE, 0, 0); + if (dbuf_store == NULL) { + retval = IDM_STATUS_FAIL; + goto tear_down_and_return; + } + dbuf_store->ds_alloc_data_buf = iscsit_dbuf_alloc; + dbuf_store->ds_free_data_buf = iscsit_dbuf_free; + dbuf_store->ds_port_private = NULL; + iscsit_global.global_dbuf_store = dbuf_store; + + /* Status PDU cache */ + iscsit_status_pdu_cache = kmem_cache_create("iscsit_status_pdu_cache", + sizeof (idm_pdu_t) + sizeof (iscsi_scsi_rsp_hdr_t), 8, + &iscsit_status_pdu_constructor, + NULL, NULL, NULL, NULL, KM_SLEEP); + + /* Default TPG and portal */ + iscsit_global.global_default_tpg = iscsit_tpg_createdefault(); + if (iscsit_global.global_default_tpg == NULL) { + retval = IDM_STATUS_FAIL; + goto tear_down_and_return; + } + + /* initialize isns client */ + (void) iscsit_isns_init(hostinfo); + did_iscsit_isns_init = B_TRUE; + + /* Register port provider */ + pp = stmf_alloc(STMF_STRUCT_PORT_PROVIDER, 0, 0); + if (pp == NULL) { + retval = IDM_STATUS_FAIL; + goto tear_down_and_return; + } + + pp->pp_portif_rev = PORTIF_REV_1; + pp->pp_instance = 0; + pp->pp_name = ISCSIT_MODNAME; + pp->pp_cb = iscsit_pp_cb; + + iscsit_global.global_pp = pp; + + + if (stmf_register_port_provider(pp) != STMF_SUCCESS) { + retval = IDM_STATUS_FAIL; + goto tear_down_and_return; + } + + iscsit_global.global_dispatch_taskq = taskq_create("iscsit_dispatch", + 1, minclsyspri, 16, 16, TASKQ_PREPOPULATE); + + return (IDM_STATUS_SUCCESS); + +tear_down_and_return: + + if (iscsit_global.global_dispatch_taskq) { + taskq_destroy(iscsit_global.global_dispatch_taskq); + iscsit_global.global_dispatch_taskq = NULL; + } + + if (did_iscsit_isns_init) + iscsit_isns_fini(); + + if (iscsit_global.global_default_tpg) { + iscsit_tpg_destroydefault(iscsit_global.global_default_tpg); + iscsit_global.global_default_tpg = NULL; + } + + if (iscsit_global.global_pp) + iscsit_global.global_pp = NULL; + + if (pp) + stmf_free(pp); + + if (iscsit_status_pdu_cache) { + kmem_cache_destroy(iscsit_status_pdu_cache); + iscsit_status_pdu_cache = NULL; + } + + if (iscsit_global.global_dbuf_store) { + stmf_free(iscsit_global.global_dbuf_store); + iscsit_global.global_dbuf_store = NULL; + } + + if (iscsit_global.global_tsih_pool) { + vmem_destroy(iscsit_global.global_tsih_pool); + iscsit_global.global_tsih_pool = NULL; + } + + avl_destroy(&iscsit_global.global_ini_list); + avl_destroy(&iscsit_global.global_tpg_list); + list_destroy(&iscsit_global.global_deleted_target_list); + avl_destroy(&iscsit_global.global_target_list); + avl_destroy(&iscsit_global.global_discovery_sessions); + + idm_refcnt_destroy(&iscsit_global.global_refcnt); + + return (retval); +} + +/* + * iscsit_disable_svc + * + * clean up all existing connections and deregister targets from STMF + */ +static void +iscsit_disable_svc(void) +{ + iscsit_sess_t *sess; + + ASSERT(iscsit_global.global_svc_state == ISE_DISABLING); + + /* tear down discovery sessions */ + for (sess = avl_first(&iscsit_global.global_discovery_sessions); + sess != NULL; + sess = AVL_NEXT(&iscsit_global.global_discovery_sessions, sess)) + iscsit_sess_close(sess); + + /* + * Passing NULL to iscsit_config_merge tells it to go to an empty + * config. + */ + (void) iscsit_config_merge(NULL); + + /* + * Wait until there are no more global references + */ + idm_refcnt_wait_ref(&iscsit_global.global_refcnt); + idm_refcnt_destroy(&iscsit_global.global_refcnt); + + /* + * Default TPG must be destroyed after global_refcnt is 0. + */ + iscsit_tpg_destroydefault(iscsit_global.global_default_tpg); + + avl_destroy(&iscsit_global.global_discovery_sessions); + list_destroy(&iscsit_global.global_deleted_target_list); + avl_destroy(&iscsit_global.global_target_list); + avl_destroy(&iscsit_global.global_tpg_list); + avl_destroy(&iscsit_global.global_ini_list); + + taskq_destroy(iscsit_global.global_dispatch_taskq); + + iscsit_isns_fini(); + + stmf_free(iscsit_global.global_dbuf_store); + iscsit_global.global_dbuf_store = NULL; + + (void) stmf_deregister_port_provider(iscsit_global.global_pp); + stmf_free(iscsit_global.global_pp); + iscsit_global.global_pp = NULL; + + kmem_cache_destroy(iscsit_status_pdu_cache); + iscsit_status_pdu_cache = NULL; + + vmem_destroy(iscsit_global.global_tsih_pool); + iscsit_global.global_tsih_pool = NULL; +} + +void +iscsit_global_hold() +{ + idm_refcnt_hold(&iscsit_global.global_refcnt); +} + +void +iscsit_global_rele() +{ + idm_refcnt_rele(&iscsit_global.global_refcnt); +} + +void +iscsit_global_wait_ref() +{ + idm_refcnt_wait_ref(&iscsit_global.global_refcnt); +} + +/* + * IDM callbacks + */ + +/*ARGSUSED*/ +void +iscsit_rx_pdu(idm_conn_t *ic, idm_pdu_t *rx_pdu) +{ + iscsit_conn_t *ict = ic->ic_handle; + switch (IDM_PDU_OPCODE(rx_pdu)) { + case ISCSI_OP_SCSI_CMD: + ASSERT(0); /* Shouldn't happen */ + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); + break; + case ISCSI_OP_SNACK_CMD: + /* + * We'll need to handle this when we support ERL1/2. For + * now we treat it as a protocol error. + */ + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); + idm_conn_event(ic, CE_TRANSPORT_FAIL, NULL); + break; + case ISCSI_OP_SCSI_TASK_MGT_MSG: + iscsit_set_cmdsn(ict, rx_pdu); + iscsit_op_scsi_task_mgmt(ict, rx_pdu); + break; + case ISCSI_OP_NOOP_OUT: + case ISCSI_OP_LOGIN_CMD: + case ISCSI_OP_TEXT_CMD: + case ISCSI_OP_LOGOUT_CMD: + /* + * If/when we switch to userland processing these PDU's + * will be handled by iscsitd. + */ + iscsit_deferred_dispatch(rx_pdu); + break; + default: + /* Protocol error */ + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); + idm_conn_event(ic, CE_TRANSPORT_FAIL, NULL); + break; + } +} + +/*ARGSUSED*/ +void +iscsit_rx_pdu_error(idm_conn_t *ic, idm_pdu_t *rx_pdu, idm_status_t status) +{ + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); +} + +void +iscsit_task_aborted(idm_task_t *idt, idm_status_t status) +{ + iscsit_task_t *itask = idt->idt_private; + + switch (status) { + case IDM_STATUS_SUSPENDED: + break; + case IDM_STATUS_ABORTED: + mutex_enter(&itask->it_mutex); + itask->it_aborted = B_TRUE; + if (itask->it_stmf_abort) { + mutex_exit(&itask->it_mutex); + /* + * STMF has already asked for this task to be aborted. + * Cleanup the task and call STMF to let it know that + * the abort is complete. + */ + idm_task_cleanup(idt); + + /* + * STMF specification is wrong... says to return + * STMF_ABORTED, the code actually looks for + * STMF_ABORT_SUCCESS. + */ + stmf_task_lport_aborted(itask->it_stmf_task, + STMF_ABORT_SUCCESS, STMF_IOF_LPORT_DONE); + return; + } else { + mutex_exit(&itask->it_mutex); + /* + * Tell STMF to stop processing the task. We will + * cleanup the task in the context of iscsit_abort + * (lport_abort). + */ + idm_task_cleanup(idt); + stmf_abort(STMF_QUEUE_TASK_ABORT, itask->it_stmf_task, + STMF_ABORTED, NULL); + return; + } + /*NOTREACHED*/ + default: + ASSERT(0); + } +} + +/*ARGSUSED*/ +idm_status_t +iscsit_client_notify(idm_conn_t *ic, idm_client_notify_t icn, + uintptr_t data) +{ + idm_status_t rc = IDM_STATUS_SUCCESS; + + /* + * IDM client notifications will never occur at interrupt level + * since they are generated from the connection state machine which + * running on taskq threads. + * + */ + switch (icn) { + case CN_CONNECT_ACCEPT: + rc = iscsit_conn_accept(ic); /* No data */ + break; + case CN_FFP_ENABLED: + rc = iscsit_ffp_enabled(ic); /* No data */ + break; + case CN_FFP_DISABLED: + /* + * Data indicates whether this was the result of an + * explicit logout request. + */ + rc = iscsit_ffp_disabled(ic, (idm_ffp_disable_t)data); + break; + case CN_CONNECT_LOST: + rc = iscsit_conn_lost(ic); + break; + case CN_CONNECT_DESTROY: + rc = iscsit_conn_destroy(ic); + break; + case CN_LOGIN_FAIL: + /* + * Force the login state machine to completion + */ + rc = iscsit_login_fail(ic); + break; + default: + rc = IDM_STATUS_REJECT; + break; + } + + return (rc); +} + + +void +iscsit_build_hdr(idm_task_t *idm_task, idm_pdu_t *pdu, uint8_t opcode) +{ + iscsit_task_t *itask = idm_task->idt_private; + iscsi_data_rsp_hdr_t *dh = (iscsi_data_rsp_hdr_t *)pdu->isp_hdr; + + /* + * We acquired iscsit_sess_t.ist_sn_rwlock in iscsit_xfer_scsi_data + * in reader mode so we expect to be locked here + */ + + /* + * Lun is only required if the opcode == ISCSI_OP_SCSI_DATA_RSP + * and the 'A' bit is to be set + */ + dh->opcode = opcode; + dh->itt = itask->it_itt; + dh->ttt = itask->it_ttt; + /* Statsn is only set during phase collapse */ + dh->expcmdsn = htonl(itask->it_ict->ict_sess->ist_expcmdsn); + dh->maxcmdsn = htonl(itask->it_ict->ict_sess->ist_maxcmdsn); + + /* + * IDM must set: + * + * data.flags and rtt.flags + * data.dlength + * data.datasn + * data.offset + * residual_count and cmd_status (if we ever implement phase collapse) + * rtt.rttsn + * rtt.data_offset + * rtt.data_length + */ +} + +static idm_status_t +iscsit_conn_accept(idm_conn_t *ic) +{ + iscsit_conn_t *ict; + + /* + * Allocate an associated iscsit structure to represent this + * connection. We shouldn't really create a session until we + * get the first login PDU. + */ + ict = kmem_zalloc(sizeof (*ict), KM_SLEEP); + + ict->ict_ic = ic; + ict->ict_statsn = 1; + ic->ic_handle = ict; + mutex_init(&ict->ict_mutex, NULL, MUTEX_DRIVER, NULL); + idm_refcnt_init(&ict->ict_refcnt, ict); + + /* + * Initialize login state machine + */ + if (iscsit_login_sm_init(ict) != IDM_STATUS_SUCCESS) { + return (IDM_STATUS_FAIL); + } + + return (IDM_STATUS_SUCCESS); +} + +idm_status_t +iscsit_conn_reinstate(iscsit_conn_t *reinstate_ict, iscsit_conn_t *new_ict) +{ + idm_status_t result; + + /* + * Note in new connection state that this connection is + * reinstating an existing connection. + */ + new_ict->ict_reinstating = B_TRUE; + new_ict->ict_reinstate_conn = reinstate_ict; + new_ict->ict_statsn = reinstate_ict->ict_statsn; + + /* + * Now generate connection state machine event to existing connection + * so that it starts the cleanup process. + */ + result = idm_conn_reinstate_event(reinstate_ict->ict_ic, + new_ict->ict_ic); + + return (result); +} + +void +iscsit_conn_hold(iscsit_conn_t *ict) +{ + idm_refcnt_hold(&ict->ict_refcnt); +} + +void +iscsit_conn_rele(iscsit_conn_t *ict) +{ + idm_refcnt_rele(&ict->ict_refcnt); +} + +void +iscsit_conn_dispatch_hold(iscsit_conn_t *ict) +{ + idm_refcnt_hold(&ict->ict_dispatch_refcnt); +} + +void +iscsit_conn_dispatch_rele(iscsit_conn_t *ict) +{ + idm_refcnt_rele(&ict->ict_dispatch_refcnt); +} + +static idm_status_t +iscsit_login_fail(idm_conn_t *ic) +{ + iscsit_conn_t *ict = ic->ic_handle; + + /* Generate login state machine event */ + iscsit_login_sm_event(ict, ILE_LOGIN_CONN_ERROR, NULL); + + return (IDM_STATUS_SUCCESS); +} + +static idm_status_t +iscsit_ffp_enabled(idm_conn_t *ic) +{ + iscsit_conn_t *ict = ic->ic_handle; + + /* Generate session state machine event */ + iscsit_sess_sm_event(ict->ict_sess, SE_CONN_LOGGED_IN, ict); + + return (IDM_STATUS_SUCCESS); +} + +static idm_status_t +iscsit_ffp_disabled(idm_conn_t *ic, idm_ffp_disable_t disable_class) +{ + iscsit_conn_t *ict = ic->ic_handle; + + /* Generate session state machine event */ + switch (disable_class) { + case FD_CONN_FAIL: + iscsit_sess_sm_event(ict->ict_sess, SE_CONN_FFP_FAIL, ict); + break; + case FD_CONN_LOGOUT: + iscsit_sess_sm_event(ict->ict_sess, SE_CONN_FFP_DISABLE, ict); + break; + case FD_SESS_LOGOUT: + iscsit_sess_sm_event(ict->ict_sess, SE_SESSION_CLOSE, ict); + break; + default: + ASSERT(0); + } + + return (IDM_STATUS_SUCCESS); +} + +static idm_status_t +iscsit_conn_lost(idm_conn_t *ic) +{ + iscsit_conn_t *ict = ic->ic_handle; + + mutex_enter(&ict->ict_mutex); + ict->ict_lost = B_TRUE; + mutex_exit(&ict->ict_mutex); + + /* + * Make sure there aren't any PDU's transitioning from the receive + * handler to the dispatch taskq. + */ + idm_refcnt_wait_ref(&ict->ict_dispatch_refcnt); + + return (IDM_STATUS_SUCCESS); +} + +static idm_status_t +iscsit_conn_destroy(idm_conn_t *ic) +{ + iscsit_conn_t *ict = ic->ic_handle; + + mutex_enter(&ict->ict_mutex); + ict->ict_destroyed = B_TRUE; + mutex_exit(&ict->ict_mutex); + + /* Generate session state machine event */ + if (ict->ict_sess != NULL) { + /* + * Session state machine will call iscsit_conn_destroy_done() + * when it has removed references to this connection. + */ + iscsit_sess_sm_event(ict->ict_sess, SE_CONN_FAIL, ict); + } + + ict->ict_ic = NULL; + + idm_refcnt_wait_ref(&ict->ict_refcnt); + + mutex_destroy(&ict->ict_mutex); + idm_refcnt_destroy(&ict->ict_refcnt); + kmem_free(ict, sizeof (*ict)); + + return (IDM_STATUS_SUCCESS); +} + +/* + * STMF-related functions + * + * iSCSI to STMF mapping + * + * Session == ? + * Connection == bound to local port but not itself a local port + * Target + * Target portal (group?) == local port (really but we're not going to do this) + * iscsit needs to map connections to local ports (whatever we decide + * they are) + * Target == ? + */ + +/*ARGSUSED*/ +static stmf_data_buf_t * +iscsit_dbuf_alloc(scsi_task_t *task, uint32_t size, uint32_t *pminsize, + uint32_t flags) +{ + iscsit_task_t *itask = task->task_port_private; + idm_buf_t *idm_buffer; + iscsit_buf_t *ibuf; + stmf_data_buf_t *result; + + /* + * Once the iSER picture is better understood it might make sense + * to pre-allocate some registered buffers, similar to what + * fct/qlt are doing. In the meantime hopefully stmf can allocate + * these things quickly. + * + * We can't support a transfer larger than MaxBurstLength bytes. + */ + if (size > itask->it_ict->ict_op.op_max_burst_length) { + *pminsize = itask->it_ict->ict_op.op_max_burst_length; + return (NULL); + } + + /* Alloc buffer */ + idm_buffer = idm_buf_alloc(itask->it_ict->ict_ic, NULL, size); + if (idm_buffer != NULL) { + result = stmf_alloc(STMF_STRUCT_DATA_BUF, + sizeof (iscsit_buf_t), 0); + if (result != NULL) { + /* Fill in stmf_data_buf_t */ + ibuf = result->db_port_private; + ibuf->ibuf_idm_buf = idm_buffer; + ibuf->ibuf_stmf_buf = result; + ibuf->ibuf_is_immed = B_FALSE; + result->db_flags = DB_DONT_CACHE; + result->db_buf_size = size; + result->db_data_size = size; + result->db_sglist_length = 1; + result->db_sglist[0].seg_addr = idm_buffer->idb_buf; + result->db_sglist[0].seg_length = + idm_buffer->idb_buflen; + return (result); + } + + /* Couldn't get the stmf_data_buf_t so free the buffer */ + idm_buf_free(idm_buffer); + } + + return (NULL); +} + +/*ARGSUSED*/ +static void +iscsit_dbuf_free(stmf_dbuf_store_t *ds, stmf_data_buf_t *dbuf) +{ + iscsit_buf_t *ibuf = dbuf->db_port_private; + + if (ibuf->ibuf_is_immed) { + /* + * The iscsit_buf_t structure itself will be freed with its + * associated task. Here we just need to free the PDU that + * held the immediate data. + */ + idm_pdu_complete(ibuf->ibuf_immed_data_pdu, IDM_STATUS_SUCCESS); + ibuf->ibuf_immed_data_pdu = 0; + } else { + idm_buf_free(ibuf->ibuf_idm_buf); + stmf_free(dbuf); + } +} + +/*ARGSUSED*/ +stmf_status_t +iscsit_xfer_scsi_data(scsi_task_t *task, stmf_data_buf_t *dbuf, + uint32_t ioflags) +{ + iscsit_task_t *iscsit_task = task->task_port_private; + iscsit_buf_t *ibuf = dbuf->db_port_private; + int idm_rc; + + /* + * If it's not immediate data then start the transfer + */ + ASSERT(ibuf->ibuf_is_immed == B_FALSE); + if (dbuf->db_flags & DB_DIRECTION_TO_RPORT) { + + /* + * IDM will call iscsit_build_hdr so lock now to serialize + * access to the SN values. We need to lock here to enforce + * lock ordering + */ + rw_enter(&iscsit_task->it_ict->ict_sess->ist_sn_rwlock, + RW_READER); + idm_rc = idm_buf_tx_to_ini(iscsit_task->it_idm_task, + ibuf->ibuf_idm_buf, dbuf->db_relative_offset, + dbuf->db_data_size, &iscsit_buf_xfer_cb, dbuf); + rw_exit(&iscsit_task->it_ict->ict_sess->ist_sn_rwlock); + + return (iscsit_idm_to_stmf(idm_rc)); + } else if (dbuf->db_flags & DB_DIRECTION_FROM_RPORT) { + /* Grab the SN lock (see comment above) */ + rw_enter(&iscsit_task->it_ict->ict_sess->ist_sn_rwlock, + RW_READER); + idm_rc = idm_buf_rx_from_ini(iscsit_task->it_idm_task, + ibuf->ibuf_idm_buf, dbuf->db_relative_offset, + dbuf->db_data_size, &iscsit_buf_xfer_cb, dbuf); + rw_exit(&iscsit_task->it_ict->ict_sess->ist_sn_rwlock); + + return (iscsit_idm_to_stmf(idm_rc)); + } + + /* What are we supposed to do if there is no direction? */ + return (STMF_INVALID_ARG); +} + +static void +iscsit_buf_xfer_cb(idm_buf_t *idb, idm_status_t status) +{ + iscsit_task_t *itask = idb->idb_task_binding->idt_private; + stmf_data_buf_t *dbuf = idb->idb_cb_arg; + + dbuf->db_xfer_status = iscsit_idm_to_stmf(status); + + /* + * COMSTAR currently requires port providers to support + * the DB_SEND_STATUS_GOOD flag even if phase collapse is + * not supported. So we will roll our own... pretend we are + * COMSTAR and ask for a status PDU. + */ + if ((dbuf->db_flags & DB_SEND_STATUS_GOOD) && + status == IDM_STATUS_SUCCESS) { + /* + * If iscsit_send_scsi_status succeeds then the TX PDU + * callback will call stmf_send_status_done and set + * STMF_IOF_LPORT_DONE. Consequently we don't need + * to call stmf_data_xfer_done in that case. We + * still need to call it if we get a failure. + * + * To elaborate on this some more, upon successful return + * from iscsit_send_scsi_status it's possible that itask + * and idb have been freed and are no longer valid. + */ + if (iscsit_send_scsi_status(itask->it_stmf_task, 0) + != IDM_STATUS_SUCCESS) { + /* Failed to send status */ + dbuf->db_xfer_status = STMF_FAILURE; + stmf_data_xfer_done(itask->it_stmf_task, dbuf, + STMF_IOF_LPORT_DONE); + } + } else { + stmf_data_xfer_done(itask->it_stmf_task, dbuf, 0); + } +} + +/*ARGSUSED*/ +stmf_status_t +iscsit_send_scsi_status(scsi_task_t *task, uint32_t ioflags) +{ + iscsit_task_t *itask = task->task_port_private; + iscsi_scsi_rsp_hdr_t *rsp; + idm_pdu_t *pdu; + int resp_datalen; + + /* + * If this task is aborted then we don't need to respond. + */ + if (itask->it_stmf_abort) { + stmf_send_status_done(task, STMF_SUCCESS, + STMF_IOF_LPORT_DONE); + return (STMF_SUCCESS); + } + + /* + * If this is a task management status, handle it elsewhere. + */ + if (task->task_mgmt_function != TM_NONE) { + /* + * Don't wait for the PDU completion to tell STMF + * the task is done -- it doesn't really matter and + * it makes life complicated if STMF later asks us to + * abort the request and we don't know whether the + * status has been sent or not. + */ + itask->it_tm_responded = B_TRUE; + iscsit_send_task_mgmt_resp(itask->it_tm_pdu, + (task->task_completion_status == STMF_SUCCESS) ? + SCSI_TCP_TM_RESP_COMPLETE : SCSI_TCP_TM_RESP_FUNC_NOT_SUPP); + stmf_send_status_done(task, STMF_SUCCESS, + STMF_IOF_LPORT_DONE); + return (STMF_SUCCESS); + } + + mutex_enter(&itask->it_idm_task->idt_mutex); + if ((itask->it_idm_task->idt_state == TASK_ACTIVE) && + (task->task_completion_status == STMF_SUCCESS) && + (task->task_sense_length == 0) && + (task->task_resid == 0)) { + itask->it_idm_task->idt_state = TASK_COMPLETE; + /* PDU callback releases task hold */ + idm_task_hold(itask->it_idm_task); + mutex_exit(&itask->it_idm_task->idt_mutex); + /* + * Fast path. Cached status PDU's are already + * initialized. We just need to fill in + * connection and task information. + */ + pdu = kmem_cache_alloc(iscsit_status_pdu_cache, KM_SLEEP); + pdu->isp_ic = itask->it_ict->ict_ic; + pdu->isp_private = itask; + + rsp = (iscsi_scsi_rsp_hdr_t *)pdu->isp_hdr; + rsp->itt = itask->it_itt; + rsp->cmd_status = task->task_scsi_status; + iscsit_pdu_tx(pdu); + return (STMF_SUCCESS); + } else { + if (itask->it_idm_task->idt_state != TASK_ACTIVE) { + mutex_exit(&itask->it_idm_task->idt_mutex); + return (IDM_STATUS_FAIL); + } + itask->it_idm_task->idt_state = TASK_COMPLETE; + /* PDU callback releases task hold */ + idm_task_hold(itask->it_idm_task); + mutex_exit(&itask->it_idm_task->idt_mutex); + + resp_datalen = (task->task_sense_length == 0) ? 0 : + (task->task_sense_length + sizeof (uint16_t)); + + pdu = idm_pdu_alloc(sizeof (iscsi_hdr_t), resp_datalen); + idm_pdu_init(pdu, itask->it_ict->ict_ic, itask, + iscsit_send_status_done); + + rsp = (iscsi_scsi_rsp_hdr_t *)pdu->isp_hdr; + bzero(rsp, sizeof (*rsp)); + rsp->opcode = ISCSI_OP_SCSI_RSP; + + rsp->flags = ISCSI_FLAG_FINAL; + if (task->task_status_ctrl & TASK_SCTRL_OVER) { + rsp->flags |= ISCSI_FLAG_CMD_OVERFLOW; + } else if (task->task_status_ctrl & TASK_SCTRL_UNDER) { + rsp->flags |= ISCSI_FLAG_CMD_UNDERFLOW; + } + + rsp->bi_residual_count = 0; + rsp->residual_count = htonl(task->task_resid); + rsp->itt = itask->it_itt; + rsp->response = ISCSI_STATUS_CMD_COMPLETED; + rsp->cmd_status = task->task_scsi_status; + if (task->task_sense_length != 0) { + /* + * Add a byte to provide the sense length in + * the response + */ + *(uint16_t *)((void *)pdu->isp_data) = + htons(task->task_sense_length); + bcopy(task->task_sense_data, + (uint8_t *)pdu->isp_data + + sizeof (uint16_t), + task->task_sense_length); + hton24(rsp->dlength, resp_datalen); + } + + iscsit_pdu_tx(pdu); + + return (STMF_SUCCESS); + } +} + +/*ARGSUSED*/ +static void +iscsit_send_good_status_done(idm_pdu_t *pdu, idm_status_t status) +{ + iscsit_task_t *itask; + + itask = pdu->isp_private; + idm_task_rele(itask->it_idm_task); + stmf_send_status_done(itask->it_stmf_task, + iscsit_idm_to_stmf(pdu->isp_status), STMF_IOF_LPORT_DONE); + kmem_cache_free(iscsit_status_pdu_cache, pdu); +} + +/*ARGSUSED*/ +static void +iscsit_send_status_done(idm_pdu_t *pdu, idm_status_t status) +{ + iscsit_task_t *itask; + + itask = pdu->isp_private; + idm_task_rele(itask->it_idm_task); + stmf_send_status_done(itask->it_stmf_task, + iscsit_idm_to_stmf(pdu->isp_status), STMF_IOF_LPORT_DONE); + idm_pdu_free(pdu); +} + + +void +iscsit_lport_task_free(scsi_task_t *task) +{ + iscsit_task_t *itask = task->task_port_private; + + /* We only call idm_task_start for regular tasks, not task management */ + if (task->task_mgmt_function == TM_NONE) { + idm_task_done(itask->it_idm_task); + iscsit_task_free(itask); + return; + } else { + iscsit_tm_task_free(itask); + } +} + +/*ARGSUSED*/ +stmf_status_t +iscsit_abort(stmf_local_port_t *lport, int abort_cmd, void *arg, uint32_t flags) +{ + scsi_task_t *st = (scsi_task_t *)arg; + iscsit_task_t *iscsit_task; + idm_task_t *idt; + + /* + * If this is a task management request then there's really not much to + * do. + */ + if (st->task_mgmt_function != TM_NONE) { + return (STMF_ABORT_SUCCESS); + } + + /* + * Regular task, start cleaning up + */ + iscsit_task = st->task_port_private; + idt = iscsit_task->it_idm_task; + mutex_enter(&iscsit_task->it_mutex); + iscsit_task->it_stmf_abort = B_TRUE; + if (iscsit_task->it_aborted) { + mutex_exit(&iscsit_task->it_mutex); + /* + * STMF specification is wrong... says to return + * STMF_ABORTED, the code actually looks for + * STMF_ABORT_SUCCESS. + */ + return (STMF_ABORT_SUCCESS); + } else { + mutex_exit(&iscsit_task->it_mutex); + /* + * Call IDM to abort the task. Due to a variety of + * circumstances the task may already be in the process of + * aborting. + * We'll let IDM worry about rationalizing all that except + * for one particular instance. If the state of the task + * is TASK_COMPLETE, we need to indicate to the framework + * that we are in fact done. This typically happens with + * framework-initiated task management type requests + * (e.g. abort task). + */ + if (idt->idt_state == TASK_COMPLETE) { + idm_refcnt_wait_ref(&idt->idt_refcnt); + return (STMF_ABORT_SUCCESS); + } else { + idm_task_abort(idt->idt_ic, idt, AT_TASK_MGMT_ABORT); + return (STMF_SUCCESS); + } + } + + /*NOTREACHED*/ +} + +/*ARGSUSED*/ +void +iscsit_ctl(stmf_local_port_t *lport, int cmd, void *arg) +{ + iscsit_tgt_t *iscsit_tgt; + + ASSERT((cmd == STMF_CMD_LPORT_ONLINE) || + (cmd == STMF_ACK_LPORT_ONLINE_COMPLETE) || + (cmd == STMF_CMD_LPORT_OFFLINE) || + (cmd == STMF_ACK_LPORT_OFFLINE_COMPLETE)); + + iscsit_tgt = (iscsit_tgt_t *)lport->lport_port_private; + + switch (cmd) { + case STMF_CMD_LPORT_ONLINE: + iscsit_tgt_sm_event(iscsit_tgt, TE_STMF_ONLINE_REQ); + break; + case STMF_CMD_LPORT_OFFLINE: + iscsit_tgt_sm_event(iscsit_tgt, TE_STMF_OFFLINE_REQ); + break; + case STMF_ACK_LPORT_ONLINE_COMPLETE: + iscsit_tgt_sm_event(iscsit_tgt, TE_STMF_ONLINE_COMPLETE_ACK); + break; + case STMF_ACK_LPORT_OFFLINE_COMPLETE: + iscsit_tgt_sm_event(iscsit_tgt, TE_STMF_OFFLINE_COMPLETE_ACK); + break; + + default: + break; + } +} + +static stmf_status_t +iscsit_idm_to_stmf(idm_status_t idmrc) +{ + switch (idmrc) { + case IDM_STATUS_SUCCESS: + return (STMF_SUCCESS); + default: + return (STMF_FAILURE); + } + /*NOTREACHED*/ +} + + +/* + * ISCSI protocol + */ + +void +iscsit_op_scsi_cmd(idm_conn_t *ic, idm_pdu_t *rx_pdu) +{ + iscsit_conn_t *ict; + iscsit_task_t *itask; + scsi_task_t *task; + iscsit_buf_t *ibuf; + iscsi_scsi_cmd_hdr_t *iscsi_scsi = + (iscsi_scsi_cmd_hdr_t *)rx_pdu->isp_hdr; + iscsi_addl_hdr_t *ahs_hdr; + uint16_t addl_cdb_len = 0; + + ict = ic->ic_handle; + + itask = iscsit_task_alloc(ict); + if (itask == NULL) { + iscsit_send_direct_scsi_resp(ict, rx_pdu, + ISCSI_STATUS_CMD_COMPLETED, STATUS_BUSY); + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); + return; + } + + /* Finish processing request */ + iscsit_set_cmdsn(ict, rx_pdu); + + /* + * Note CmdSN and ITT in task. IDM will have already validated this + * request against the connection state so we don't need to check + * that (the connection may have changed state in the meantime but + * we will catch that when we try to send a response) + */ + itask->it_cmdsn = ntohl(iscsi_scsi->cmdsn); + itask->it_itt = iscsi_scsi->itt; + + /* + * Check for extended CDB AHS + */ + if (iscsi_scsi->hlength > 0) { + ahs_hdr = (iscsi_addl_hdr_t *)iscsi_scsi; + addl_cdb_len = ((ahs_hdr->ahs_hlen_hi << 8) | + ahs_hdr->ahs_hlen_lo) - 1; /* Adjust for reserved byte */ + if (((addl_cdb_len + 4) / sizeof (uint32_t)) > + iscsi_scsi->hlength) { + /* Mangled header info, drop it */ + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); + return; + } + } + + ict = rx_pdu->isp_ic->ic_handle; /* IDM client private */ + + itask->it_stmf_task = stmf_task_alloc( + itask->it_ict->ict_sess->ist_lport, + itask->it_ict->ict_sess->ist_stmf_sess, iscsi_scsi->lun, + 16 + addl_cdb_len, 0); + if (itask->it_stmf_task == NULL) { + /* + * Either stmf really couldn't get memory for a task or, + * more likely, the LU is currently in reset. Either way + * we have no choice but to fail the request. + */ + iscsit_task_free(itask); + iscsit_send_direct_scsi_resp(ict, rx_pdu, + ISCSI_STATUS_CMD_COMPLETED, STATUS_BUSY); + idm_pdu_complete(rx_pdu, IDM_STATUS_FAIL); + return; + } + + task = itask->it_stmf_task; + task->task_port_private = itask; + + bcopy(iscsi_scsi->lun, task->task_lun_no, sizeof (task->task_lun_no)); + + /* + * iSCSI and Comstar use the same values. Should we rely on this + * or translate them bit-wise? + */ + + task->task_flags = + (((iscsi_scsi->flags & ISCSI_FLAG_CMD_READ) ? TF_READ_DATA : 0) | + ((iscsi_scsi->flags & ISCSI_FLAG_CMD_WRITE) ? TF_WRITE_DATA : 0) | + ((rx_pdu->isp_datalen == 0) ? 0 : TF_INITIAL_BURST)); + + switch (iscsi_scsi->flags & ISCSI_FLAG_CMD_ATTR_MASK) { + case ISCSI_ATTR_UNTAGGED: + break; + case ISCSI_ATTR_SIMPLE: + task->task_additional_flags |= TF_ATTR_SIMPLE_QUEUE; + break; + case ISCSI_ATTR_ORDERED: + task->task_additional_flags |= TF_ATTR_ORDERED_QUEUE; + break; + case ISCSI_ATTR_HEAD_OF_QUEUE: + task->task_additional_flags |= TF_ATTR_HEAD_OF_QUEUE; + break; + case ISCSI_ATTR_ACA: + task->task_additional_flags |= TF_ATTR_ACA; + break; + default: + /* Protocol error but just take it, treat as untagged */ + break; + } + + + task->task_additional_flags = 0; + task->task_priority = 0; + task->task_mgmt_function = TM_NONE; + + /* + * This "task_max_nbufs" doesn't map well to BIDI. We probably need + * parameter for each direction. "MaxOutstandingR2T" may very well + * be set to one which could prevent us from doing simultaneous + * transfers in each direction. + */ + task->task_max_nbufs = (iscsi_scsi->flags & ISCSI_FLAG_CMD_WRITE) ? + ict->ict_op.op_max_outstanding_r2t : STMF_BUFS_MAX; + task->task_cmd_seq_no = ntohl(iscsi_scsi->itt); + task->task_expected_xfer_length = ntohl(iscsi_scsi->data_length); + + /* Copy CDB */ + bcopy(iscsi_scsi->scb, task->task_cdb, 16); + if (addl_cdb_len > 0) { + bcopy(ahs_hdr->ahs_extscb, task->task_cdb + 16, addl_cdb_len); + } + + /* + * Copy the transport header into the task handle from the PDU + * handle. The transport header describes this task's remote tagged + * buffer. + */ + if (rx_pdu->isp_transport_hdrlen != 0) { + bcopy(rx_pdu->isp_transport_hdr, + itask->it_idm_task->idt_transport_hdr, + rx_pdu->isp_transport_hdrlen); + } + + /* + * Tell IDM about our new active task + */ + idm_task_start(itask->it_idm_task, (uintptr_t)itask->it_itt); + + /* + * If we have any immediate data then setup the immediate buffer + * context that comes with the task + */ + if (rx_pdu->isp_datalen) { + ibuf = itask->it_immed_data; + ibuf->ibuf_immed_data_pdu = rx_pdu; + ibuf->ibuf_stmf_buf->db_data_size = rx_pdu->isp_datalen; + ibuf->ibuf_stmf_buf->db_buf_size = rx_pdu->isp_datalen; + ibuf->ibuf_stmf_buf->db_relative_offset = 0; + ibuf->ibuf_stmf_buf->db_sglist[0].seg_length = + rx_pdu->isp_datalen; + ibuf->ibuf_stmf_buf->db_sglist[0].seg_addr = rx_pdu->isp_data; + + stmf_post_task(task, ibuf->ibuf_stmf_buf); + } else { + stmf_post_task(task, NULL); + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); + } +} + +/*ARGSUSED*/ +void +iscsit_deferred_dispatch(idm_pdu_t *rx_pdu) +{ + iscsit_conn_t *ict = rx_pdu->isp_ic->ic_handle; + + /* + * If the connection has been lost then ignore new PDU's + */ + mutex_enter(&ict->ict_mutex); + if (ict->ict_lost) { + mutex_exit(&ict->ict_mutex); + idm_pdu_complete(rx_pdu, IDM_STATUS_FAIL); + return; + } + + /* + * Grab a hold on the connection to prevent it from going away + * between now and when the taskq function is called. + */ + iscsit_conn_dispatch_hold(ict); + mutex_exit(&ict->ict_mutex); + + if (taskq_dispatch(iscsit_global.global_dispatch_taskq, + iscsit_deferred, rx_pdu, DDI_NOSLEEP) == NULL) { + /* + * In the unlikely scenario that we couldn't get the resources + * to dispatch the PDU then just drop it. + */ + idm_pdu_complete(rx_pdu, IDM_STATUS_FAIL); + idm_conn_event(ict->ict_ic, CE_TRANSPORT_FAIL, NULL); + iscsit_conn_dispatch_rele(ict); + } +} + +static void +iscsit_deferred(void *rx_pdu_void) +{ + idm_pdu_t *rx_pdu = rx_pdu_void; + idm_conn_t *ic = rx_pdu->isp_ic; + iscsit_conn_t *ict = ic->ic_handle; + + switch (IDM_PDU_OPCODE(rx_pdu)) { + case ISCSI_OP_NOOP_OUT: + iscsit_set_cmdsn(ict, rx_pdu); + iscsit_pdu_op_noop(ict, rx_pdu); + break; + case ISCSI_OP_LOGIN_CMD: + iscsit_pdu_op_login_cmd(ict, rx_pdu); + break; + case ISCSI_OP_TEXT_CMD: + iscsit_set_cmdsn(ict, rx_pdu); + iscsit_pdu_op_text_cmd(ict, rx_pdu); + break; + case ISCSI_OP_LOGOUT_CMD: + iscsit_set_cmdsn(ict, rx_pdu); + iscsit_pdu_op_logout_cmd(ict, rx_pdu); + break; + default: + /* Protocol error. IDM should have caught this */ + idm_pdu_complete(rx_pdu, IDM_STATUS_FAIL); + ASSERT(0); + break; + } + + iscsit_conn_dispatch_rele(ict); +} + +static void +iscsit_send_direct_scsi_resp(iscsit_conn_t *ict, idm_pdu_t *rx_pdu, + uint8_t response, uint8_t cmd_status) +{ + idm_pdu_t *rsp_pdu; + idm_conn_t *ic; + iscsi_scsi_rsp_hdr_t *resp; + + ic = ict->ict_ic; + + rsp_pdu = idm_pdu_alloc(sizeof (iscsi_scsi_rsp_hdr_t), 0); + idm_pdu_init(rsp_pdu, ic, NULL, NULL); + resp = (iscsi_scsi_rsp_hdr_t *)rsp_pdu->isp_hdr; + + resp->opcode = ISCSI_OP_SCSI_RSP; + resp->flags = ISCSI_FLAG_FINAL; + resp->response = response; + resp->cmd_status = cmd_status; + resp->itt = rx_pdu->isp_hdr->itt; + + iscsit_pdu_tx(rsp_pdu); +} + +void +iscsit_send_task_mgmt_resp(idm_pdu_t *tm_resp_pdu, uint8_t tm_status) +{ + iscsi_scsi_task_mgt_rsp_hdr_t *tm_resp; + + tm_resp = (iscsi_scsi_task_mgt_rsp_hdr_t *)tm_resp_pdu->isp_hdr; + tm_resp->response = tm_status; + iscsit_pdu_tx(tm_resp_pdu); +} + +void +iscsit_op_scsi_task_mgmt(iscsit_conn_t *ict, idm_pdu_t *rx_pdu) +{ + idm_pdu_t *tm_resp_pdu; + iscsit_task_t *itask; + iscsit_task_t *tm_itask; + scsi_task_t *task; + iscsi_scsi_task_mgt_hdr_t *iscsi_tm = + (iscsi_scsi_task_mgt_hdr_t *)rx_pdu->isp_hdr; + iscsi_scsi_task_mgt_rsp_hdr_t *iscsi_tm_rsp = + (iscsi_scsi_task_mgt_rsp_hdr_t *)rx_pdu->isp_hdr; + uint32_t rtt, cmdsn, refcmdsn; + uint8_t tm_func; + + /* + * Setup response PDU (response field will get filled in later) + */ + tm_resp_pdu = idm_pdu_alloc(sizeof (iscsi_scsi_task_mgt_rsp_hdr_t), 0); + if (tm_resp_pdu == NULL) { + /* Can't respond, just drop it */ + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); + return; + } + idm_pdu_init(tm_resp_pdu, ict->ict_ic, NULL, NULL); + iscsi_tm_rsp = (iscsi_scsi_task_mgt_rsp_hdr_t *)tm_resp_pdu->isp_hdr; + bzero(iscsi_tm_rsp, sizeof (iscsi_scsi_task_mgt_rsp_hdr_t)); + iscsi_tm_rsp->opcode = ISCSI_OP_SCSI_TASK_MGT_RSP; + iscsi_tm_rsp->flags = ISCSI_FLAG_FINAL; + iscsi_tm_rsp->itt = rx_pdu->isp_hdr->itt; + + /* + * Figure out what we're being asked to do. + */ + switch (iscsi_tm->function & ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK) { + case ISCSI_TM_FUNC_ABORT_TASK: + /* + * STMF doesn't currently support the "abort task" task + * management command although it does support aborting + * an individual task. We'll get STMF to abort the task + * for us but handle the details of the task management + * command ourselves. + * + * Find the task associated with the referenced task tag. + */ + rtt = iscsi_tm->rtt; + itask = (iscsit_task_t *)idm_task_find_by_handle(ict->ict_ic, + (uintptr_t)rtt); + + if (itask == NULL) { + cmdsn = ntohl(iscsi_tm->cmdsn); + refcmdsn = ntohl(iscsi_tm->refcmdsn); + + /* + * Task was not found. If RefCmdSN is within the CmdSN + * window and less than CmdSN of the TM function, return + * "Function Complete". Otherwise, return + * "Task Does Not Exist". + */ + + if (iscsit_cmdsn_in_window(ict, refcmdsn) && + (refcmdsn < cmdsn)) { + iscsit_send_task_mgmt_resp(tm_resp_pdu, + SCSI_TCP_TM_RESP_COMPLETE); + } else { + iscsit_send_task_mgmt_resp(tm_resp_pdu, + SCSI_TCP_TM_RESP_NO_TASK); + } + } else { + + /* + * Tell STMF to abort the task. This will do no harm + * if the task is already complete. + */ + stmf_abort(STMF_QUEUE_TASK_ABORT, itask->it_stmf_task, + STMF_ABORTED, NULL); + + /* + * Make sure the task hasn't already completed + */ + mutex_enter(&itask->it_idm_task->idt_mutex); + if ((itask->it_idm_task->idt_state == TASK_COMPLETE) || + (itask->it_idm_task->idt_state == TASK_IDLE)) { + /* + * Task is complete, return "Task Does Not + * Exist" + */ + mutex_exit(&itask->it_idm_task->idt_mutex); + iscsit_send_task_mgmt_resp(tm_resp_pdu, + SCSI_TCP_TM_RESP_NO_TASK); + } else { + /* + * STMF is now aborting the task, return + * "Function Complete" + */ + mutex_exit(&itask->it_idm_task->idt_mutex); + iscsit_send_task_mgmt_resp(tm_resp_pdu, + SCSI_TCP_TM_RESP_COMPLETE); + } + idm_task_rele(itask->it_idm_task); + } + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); + return; + + case ISCSI_TM_FUNC_ABORT_TASK_SET: + tm_func = TM_ABORT_TASK_SET; + break; + + case ISCSI_TM_FUNC_CLEAR_ACA: + tm_func = TM_CLEAR_ACA; + break; + + case ISCSI_TM_FUNC_CLEAR_TASK_SET: + tm_func = TM_CLEAR_TASK_SET; + break; + + case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET: + tm_func = TM_LUN_RESET; + break; + + case ISCSI_TM_FUNC_TARGET_WARM_RESET: + tm_func = TM_TARGET_WARM_RESET; + break; + + case ISCSI_TM_FUNC_TARGET_COLD_RESET: + tm_func = TM_TARGET_COLD_RESET; + break; + + case ISCSI_TM_FUNC_TASK_REASSIGN: + /* + * We do not currently support allegiance reassignment. When + * we start supporting ERL1+, we will need to. + */ + iscsit_send_task_mgmt_resp(tm_resp_pdu, + SCSI_TCP_TM_RESP_NO_ALLG_REASSN); + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); + return; + + default: + iscsit_send_task_mgmt_resp(tm_resp_pdu, + SCSI_TCP_TM_RESP_REJECTED); + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); + return; + } + + tm_itask = iscsit_tm_task_alloc(ict); + if (tm_itask == NULL) { + iscsit_send_task_mgmt_resp(tm_resp_pdu, + SCSI_TCP_TM_RESP_REJECTED); + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); + return; + } + + + task = stmf_task_alloc(ict->ict_sess->ist_lport, + ict->ict_sess->ist_stmf_sess, iscsi_tm->lun, + 0, STMF_TASK_EXT_NONE); + if (task == NULL) { + /* + * If this happens, either the LU is in reset, couldn't + * get memory, or some other condition in which we simply + * can't complete this request. It would be nice to return + * an error code like "busy" but the closest we have is + * "rejected". + */ + iscsit_send_task_mgmt_resp(tm_resp_pdu, + SCSI_TCP_TM_RESP_REJECTED); + iscsit_tm_task_free(tm_itask); + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); + return; + } + + tm_itask->it_tm_pdu = tm_resp_pdu; + tm_itask->it_stmf_task = task; + task->task_port_private = tm_itask; + task->task_mgmt_function = tm_func; + task->task_additional_flags = TASK_AF_NO_EXPECTED_XFER_LENGTH; + task->task_priority = 0; + task->task_max_nbufs = STMF_BUFS_MAX; + task->task_cmd_seq_no = iscsi_tm->itt; + task->task_expected_xfer_length = 0; + + stmf_post_task(task, NULL); + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); +} + +static void +iscsit_pdu_op_noop(iscsit_conn_t *ict, idm_pdu_t *rx_pdu) +{ + iscsi_nop_out_hdr_t *out = (iscsi_nop_out_hdr_t *)rx_pdu->isp_hdr; + iscsi_nop_in_hdr_t *in; + int resp_datalen; + idm_pdu_t *resp; + + /* Get iSCSI session handle */ + /* Ignore the response from initiator */ + if (out->ttt != ISCSI_RSVD_TASK_TAG) + return; + + /* Allocate a PDU to respond */ + resp_datalen = ntoh24(out->dlength); + resp = idm_pdu_alloc(sizeof (iscsi_hdr_t), resp_datalen); + idm_pdu_init(resp, ict->ict_ic, NULL, NULL); + if (resp_datalen > 0) { + bcopy(rx_pdu->isp_data, resp->isp_data, resp_datalen); + } + + in = (iscsi_nop_in_hdr_t *)resp->isp_hdr; + bzero(in, sizeof (*in)); + in->opcode = ISCSI_OP_NOOP_IN; + in->flags = ISCSI_FLAG_FINAL; + bcopy(out->lun, in->lun, 8); + in->itt = out->itt; + in->ttt = ISCSI_RSVD_TASK_TAG; + hton24(in->dlength, resp_datalen); + + /* Any other field in resp to be set? */ + iscsit_pdu_tx(resp); + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); +} + +static void +iscsit_pdu_op_login_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu) +{ + + /* + * Submit PDU to login state machine. State machine will free the + * PDU. + */ + iscsit_login_sm_event(ict, ILE_LOGIN_RCV, rx_pdu); +} + +void +iscsit_pdu_op_logout_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu) +{ + iscsi_logout_hdr_t *logout_req = + (iscsi_logout_hdr_t *)rx_pdu->isp_hdr; + iscsi_logout_rsp_hdr_t *logout_rsp; + idm_pdu_t *resp; + + /* Allocate a PDU to respond */ + resp = idm_pdu_alloc(sizeof (iscsi_hdr_t), 0); + idm_pdu_init(resp, ict->ict_ic, NULL, NULL); + + /* + * Logout results in the immediate termination of all tasks except + * if the logout reason is ISCSI_LOGOUT_REASON_RECOVERY. The + * connection state machine will drive this task cleanup automatically + * so we don't need to handle that here. + */ + logout_rsp = (iscsi_logout_rsp_hdr_t *)resp->isp_hdr; + bzero(logout_rsp, sizeof (*logout_rsp)); + logout_rsp->opcode = ISCSI_OP_LOGOUT_RSP; + logout_rsp->flags = ISCSI_FLAG_FINAL; + logout_rsp->itt = logout_req->itt; + if ((logout_req->flags & ISCSI_FLAG_LOGOUT_REASON_MASK) > + ISCSI_LOGOUT_REASON_RECOVERY) { + logout_rsp->response = ISCSI_LOGOUT_RECOVERY_UNSUPPORTED; + } else { + logout_rsp->response = ISCSI_LOGOUT_SUCCESS; + } + + iscsit_pdu_tx(resp); + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); +} + +/* + * Calculate the number of outstanding commands we can process + */ +int +iscsit_cmd_window() +{ + /* Will be better later */ + return (1024); +} + +/* + * Set local registers based on incoming PDU + */ +void +iscsit_set_cmdsn(iscsit_conn_t *ict, idm_pdu_t *rx_pdu) +{ + iscsit_sess_t *ist; + iscsi_scsi_cmd_hdr_t *req; + + ist = ict->ict_sess; + + req = (iscsi_scsi_cmd_hdr_t *)rx_pdu->isp_hdr; + + rw_enter(&ist->ist_sn_rwlock, RW_WRITER); + ist->ist_expcmdsn = ntohl(req->cmdsn) + 1; + ist->ist_maxcmdsn = ntohl(req->cmdsn) + iscsit_cmd_window(); + rw_exit(&ist->ist_sn_rwlock); +} + +/* + * Update local StatSN and set SNs in response + */ +static void +iscsit_calc_rspsn(iscsit_conn_t *ict, idm_pdu_t *resp) +{ + iscsit_sess_t *ist; + iscsi_scsi_rsp_hdr_t *rsp; + + /* Get iSCSI session handle */ + ist = ict->ict_sess; + + rsp = (iscsi_scsi_rsp_hdr_t *)resp->isp_hdr; + + /* Update StatSN */ + rsp->statsn = htonl(ict->ict_statsn); + switch (IDM_PDU_OPCODE(resp)) { + case ISCSI_OP_RTT_RSP: + /* Do nothing */ + break; + case ISCSI_OP_NOOP_IN: + /* + * Refer to section 10.19.1, RFC3720. + * Advance only if target is responding initiator + */ + if (((iscsi_nop_in_hdr_t *)rsp)->ttt == ISCSI_RSVD_TASK_TAG) + ict->ict_statsn++; + break; + case ISCSI_OP_SCSI_DATA_RSP: + if (rsp->flags & ISCSI_FLAG_DATA_STATUS) + ict->ict_statsn++; + else + rsp->statsn = 0; + break; + default: + ict->ict_statsn++; + break; + } + + /* Set ExpCmdSN and MaxCmdSN */ + rsp->maxcmdsn = htonl(ist->ist_maxcmdsn); + rsp->expcmdsn = htonl(ist->ist_expcmdsn); +} + +/* + * Wrapper funtion, calls iscsi_calc_rspsn and idm_pdu_tx + */ +void +iscsit_pdu_tx(idm_pdu_t *pdu) +{ + iscsit_conn_t *ict = pdu->isp_ic->ic_handle; + + /* + * Protect ict->ict_statsn, ist->ist_maxcmdsn, and ist->ist_expcmdsn + * (which are used by iscsit_calc_rspsn) with the session mutex + * (ist->ist_sn_mutex). + */ + rw_enter(&ict->ict_sess->ist_sn_rwlock, RW_WRITER); + iscsit_calc_rspsn(ict, pdu); + idm_pdu_tx(pdu); + rw_exit(&ict->ict_sess->ist_sn_rwlock); +} + +/* + * Internal functions + */ + +void +iscsit_send_async_event(iscsit_conn_t *ict, uint8_t event) +{ + idm_pdu_t *abt; + iscsi_async_evt_hdr_t *async_abt; + + /* + * Get a PDU to build the abort request. + */ + abt = idm_pdu_alloc(sizeof (iscsi_hdr_t), 0); + if (abt == NULL) { + idm_conn_event(ict->ict_ic, CE_TRANSPORT_FAIL, NULL); + return; + } + idm_pdu_init(abt, ict->ict_ic, NULL, NULL); + + ASSERT(abt != NULL); + abt->isp_datalen = 0; + + async_abt = (iscsi_async_evt_hdr_t *)abt->isp_hdr; + bzero(async_abt, sizeof (*async_abt)); + async_abt->opcode = ISCSI_OP_ASYNC_EVENT; + async_abt->async_event = event; + async_abt->flags = ISCSI_FLAG_FINAL; + async_abt->rsvd4[0] = 0xff; + async_abt->rsvd4[1] = 0xff; + async_abt->rsvd4[2] = 0xff; + async_abt->rsvd4[3] = 0xff; + + switch (event) { + case ISCSI_ASYNC_EVENT_REQUEST_LOGOUT: + async_abt->param3 = htons(IDM_LOGOUT_SECONDS); + break; + case ISCSI_ASYNC_EVENT_SCSI_EVENT: + case ISCSI_ASYNC_EVENT_DROPPING_CONNECTION: + case ISCSI_ASYNC_EVENT_DROPPING_ALL_CONNECTIONS: + case ISCSI_ASYNC_EVENT_PARAM_NEGOTIATION: + default: + ASSERT(0); + } + + iscsit_pdu_tx(abt); +} + + +static iscsit_task_t * +iscsit_task_alloc(iscsit_conn_t *ict) +{ + iscsit_task_t *itask; + iscsit_buf_t *immed_ibuf; + + /* + * Possible items to pre-alloc if we cache iscsit_task_t's: + * + * Status PDU w/ sense buffer + * stmf_data_buf_t for immediate data + */ + itask = kmem_alloc(sizeof (iscsit_task_t) + sizeof (iscsit_buf_t) + + sizeof (stmf_data_buf_t), KM_NOSLEEP); + if (itask != NULL) { + mutex_init(&itask->it_mutex, NULL, MUTEX_DRIVER, NULL); + itask->it_aborted = itask->it_stmf_abort = + itask->it_tm_task = 0; + + immed_ibuf = (iscsit_buf_t *)(itask + 1); + bzero(immed_ibuf, sizeof (*immed_ibuf)); + immed_ibuf->ibuf_is_immed = B_TRUE; + immed_ibuf->ibuf_stmf_buf = (stmf_data_buf_t *)(immed_ibuf + 1); + + bzero(immed_ibuf->ibuf_stmf_buf, sizeof (stmf_data_buf_t)); + immed_ibuf->ibuf_stmf_buf->db_port_private = immed_ibuf; + immed_ibuf->ibuf_stmf_buf->db_sglist_length = 1; + immed_ibuf->ibuf_stmf_buf->db_flags = DB_DIRECTION_FROM_RPORT | + DB_DONT_CACHE; + itask->it_immed_data = immed_ibuf; + itask->it_idm_task = idm_task_alloc(ict->ict_ic); + if (itask->it_idm_task != NULL) { + itask->it_idm_task->idt_private = itask; + itask->it_ict = ict; + itask->it_ttt = itask->it_idm_task->idt_tt; + return (itask); + } else { + kmem_free(itask, sizeof (iscsit_task_t) + + sizeof (iscsit_buf_t) + sizeof (stmf_data_buf_t)); + } + } + + return (NULL); +} + +static void +iscsit_task_free(iscsit_task_t *itask) +{ + idm_task_free(itask->it_idm_task); + mutex_destroy(&itask->it_mutex); + kmem_free(itask, sizeof (iscsit_task_t) + + sizeof (iscsit_buf_t) + sizeof (stmf_data_buf_t)); +} + +static iscsit_task_t * +iscsit_tm_task_alloc(iscsit_conn_t *ict) +{ + iscsit_task_t *itask; + + itask = kmem_zalloc(sizeof (iscsit_task_t), KM_NOSLEEP); + if (itask != NULL) { + idm_conn_hold(ict->ict_ic); + mutex_init(&itask->it_mutex, NULL, MUTEX_DRIVER, NULL); + itask->it_aborted = itask->it_stmf_abort = + itask->it_tm_responded = 0; + itask->it_tm_pdu = NULL; + itask->it_tm_task = 1; + itask->it_ict = ict; + } + + return (itask); +} + +static void +iscsit_tm_task_free(iscsit_task_t *itask) +{ + /* + * If we responded then the call to idm_pdu_complete will free the + * PDU. Otherwise we got aborted before the TM function could + * complete and we need to free the PDU explicitly. + */ + if (itask->it_tm_pdu != NULL && !itask->it_tm_responded) + idm_pdu_free(itask->it_tm_pdu); + idm_conn_rele(itask->it_ict->ict_ic); + mutex_destroy(&itask->it_mutex); + kmem_free(itask, sizeof (iscsit_task_t)); +} + +/* + * iscsit status PDU cache + */ + +/*ARGSUSED*/ +static int +iscsit_status_pdu_constructor(void *pdu_void, void *arg, int flags) +{ + idm_pdu_t *pdu = pdu_void; + iscsi_scsi_rsp_hdr_t *rsp; + + bzero(pdu, sizeof (idm_pdu_t)); + pdu->isp_callback = iscsit_send_good_status_done; + pdu->isp_magic = IDM_PDU_MAGIC; + pdu->isp_hdr = (iscsi_hdr_t *)(pdu + 1); /* Ptr arithmetic */ + pdu->isp_hdrlen = sizeof (iscsi_hdr_t); + + /* Setup status response */ + rsp = (iscsi_scsi_rsp_hdr_t *)pdu->isp_hdr; + bzero(rsp, sizeof (*rsp)); + rsp->opcode = ISCSI_OP_SCSI_RSP; + rsp->flags = ISCSI_FLAG_FINAL; + rsp->response = ISCSI_STATUS_CMD_COMPLETED; + + return (0); +} + +/* + * iscsit private data handler + */ + +/*ARGSUSED*/ +static void +iscsit_pp_cb(struct stmf_port_provider *pp, int cmd, void *arg, uint32_t flags) +{ + it_config_t *cfg; + nvlist_t *nvl; + + if ((cmd != STMF_PROVIDER_DATA_UPDATED) || (arg == NULL)) { + return; + } + + nvl = (nvlist_t *)arg; + + /* Translate nvlist */ + if (it_nv_to_config(nvl, &cfg) != 0) { + cmn_err(CE_WARN, "Configuration is invalid"); + return; + } + + /* Update config */ + (void) iscsit_config_merge(cfg); + + it_config_free_cmn(cfg); +} + + +static it_cfg_status_t +iscsit_config_merge(it_config_t *in_cfg) +{ + it_cfg_status_t status; + it_config_t *cfg; + it_config_t tmp_cfg; + list_t tpg_del_list; + + if (in_cfg) { + cfg = in_cfg; + } else { + /* Make empty config */ + bzero(&tmp_cfg, sizeof (tmp_cfg)); + cfg = &tmp_cfg; + } + + list_create(&tpg_del_list, sizeof (iscsit_tpg_t), + offsetof(iscsit_tpg_t, tpg_delete_ln)); + + /* + * Update targets, initiator contexts, target portal groups, + * and iSNS client + */ + ISCSIT_GLOBAL_LOCK(RW_WRITER); + if (((status = iscsit_config_merge_tpg(cfg, &tpg_del_list)) + != 0) || + ((status = iscsit_config_merge_tgt(cfg)) != 0) || + ((status = iscsit_config_merge_ini(cfg)) != 0) || + ((status = isnst_config_merge(cfg)) != 0)) { + ISCSIT_GLOBAL_UNLOCK(); + return (status); + } + + /* Update other global config parameters */ + if (iscsit_global.global_props) { + nvlist_free(iscsit_global.global_props); + iscsit_global.global_props = NULL; + } + if (in_cfg) { + (void) nvlist_dup(cfg->config_global_properties, + &iscsit_global.global_props, KM_SLEEP); + } + ISCSIT_GLOBAL_UNLOCK(); + + iscsit_config_destroy_tpgs(&tpg_del_list); + + list_destroy(&tpg_del_list); + + return (ITCFG_SUCCESS); +} + +/* + * iscsit_sna_lt[e] + * + * Compare serial numbers using serial number arithmetic as defined in + * RFC 1982. + * + * NOTE: This code is duplicated in the isns server as well as iscsitgtd. It + * ought to be common. + */ + +static int +iscsit_sna_lt(uint32_t sn1, uint32_t sn2) +{ + return ((sn1 != sn2) && + (((sn1 < sn2) && ((sn2 - sn1) < ISCSIT_SNA32_CHECK)) || + ((sn1 > sn2) && ((sn1 - sn2) > ISCSIT_SNA32_CHECK)))); +} + +static int +iscsit_sna_lte(uint32_t sn1, uint32_t sn2) +{ + return ((sn1 == sn2) || + (((sn1 < sn2) && ((sn2 - sn1) < ISCSIT_SNA32_CHECK)) || + ((sn1 > sn2) && ((sn1 - sn2) > ISCSIT_SNA32_CHECK)))); +} + + +static boolean_t +iscsit_cmdsn_in_window(iscsit_conn_t *ict, uint32_t cmdsn) +{ + iscsit_sess_t *ist = ict->ict_sess; + int rval = B_TRUE; + + ist = ict->ict_sess; + + rw_enter(&ist->ist_sn_rwlock, RW_READER); + + /* + * If cmdsn is less than ist_expcmdsn - iscsit_cmd_window() or + * greater than ist_expcmdsn, it's not in the window. + */ + + if (iscsit_sna_lt(cmdsn, (ist->ist_expcmdsn - iscsit_cmd_window())) || + !iscsit_sna_lte(cmdsn, ist->ist_expcmdsn)) { + rval = B_FALSE; + } + + rw_exit(&ist->ist_sn_rwlock); + + return (rval); +} diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit.conf b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.conf new file mode 100644 index 000000000000..14698062f6d6 --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.conf @@ -0,0 +1,27 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# + +name="iscsit" parent="/pseudo"; diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit.h b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.h new file mode 100644 index 000000000000..2fcee4fa1f3e --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.h @@ -0,0 +1,799 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#ifndef _ISCSIT_H_ +#define _ISCSIT_H_ + +#include +#include +#include + +/* + * For some reason iscsi_protocol.h lists the max version as "0x02" and the + * min version as "0x00". RFC3720 clearly states that the current version + * number is 0x00 so that is what we will use. + */ +#define ISCSIT_MIN_VERSION 0x00 +#define ISCSIT_MAX_VERSION 0x00 +#define ISCSIT_MAX_CONNECTIONS 1 /* No MC/S support */ +#define ISCSIT_MAX_RECV_DATA_SEGMENT_LENGTH (32*1024) +#define ISCSIT_MAX_BURST_LENGTH (512*1024) +#define ISCSIT_MAX_FIRST_BURST_LENGTH ISCSI_DEFAULT_FIRST_BURST_LENGTH +#define ISCSIT_MAX_TIME2WAIT ISCSI_DEFAULT_TIME_TO_WAIT +#define ISCSIT_MAX_TIME2RETAIN ISCSI_DEFAULT_TIME_TO_RETAIN +#define ISCSIT_MAX_OUTSTANDING_R2T ISCSI_DEFAULT_MAX_OUT_R2T +#define ISCSIT_MAX_ERROR_RECOVERY_LEVEL 0 + +#define ISCSIT_DEFAULT_TPG "iscsit-default-tpg" +#define ISCSIT_DEFAULT_TPGT 1 + +#define ISCSI_MAX_TSIH 0xffff +#define ISCSI_UNSPEC_TSIH 0 + +/* Max targets per system */ +#define ISCSIT_MAX_TARGETS 1024 + +/* Time in seconds to wait between calls to stmf_deregister_local_port */ +#define TGT_DEREG_RETRY_SECONDS 1 + +#define ISCSIT_GLOBAL_LOCK(rw) rw_enter(&iscsit_global.global_rwlock, (rw)) +#define ISCSIT_GLOBAL_UNLOCK() rw_exit(&iscsit_global.global_rwlock) + +/* + * Used for serial number arithmetic (RFC 1982) + */ +#define ISCSIT_SNA32_CHECK 0x80000000 + +typedef struct { + char tpg_name[MAX_TPG_NAMELEN]; + kmutex_t tpg_mutex; + idm_refcnt_t tpg_refcnt; + int tpg_online; + avl_tree_t tpg_portal_list; + avl_node_t tpg_global_ln; + list_node_t tpg_delete_ln; +} iscsit_tpg_t; + +#define IS_DEFAULT_TPGT(TPGT) \ + (((TPGT) != NULL) && \ + ((TPGT)->tpgt_tpg == iscsit_global.global_default_tpg)) + +typedef struct { + iscsit_tpg_t *tpgt_tpg; + idm_refcnt_t tpgt_refcnt; + avl_node_t tpgt_tgt_ln; + list_node_t tpgt_delete_ln; + uint16_t tpgt_tag; + boolean_t tpgt_needs_tpg_offline; +} iscsit_tpgt_t; + +typedef struct { + struct sockaddr_storage portal_addr; + int portal_online; + idm_refcnt_t portal_refcnt; + avl_node_t portal_tpg_ln; + iscsit_tpg_t *portal_tpg; + idm_svc_t *portal_svc; +} iscsit_portal_t; + + +/* Target states and events, update iscsit_ts_name table whenever modified */ +typedef enum { + TS_UNDEFINED = 0, + TS_CREATED, + TS_ONLINING, + TS_ONLINE, + TS_STMF_ONLINE, + TS_DELETING_NEED_OFFLINE, + TS_OFFLINING, + TS_OFFLINE, + TS_STMF_OFFLINE, + TS_DELETING_STMF_DEREG, + TS_DELETING_STMF_DEREG_FAIL, + TS_DELETING, + TS_MAX_STATE +} iscsit_tgt_state_t; + +#ifdef ISCSIT_TGT_SM_STRINGS +static const char *iscsit_ts_name[TS_MAX_STATE+1] = { + "TS_UNDEFINED", + "TS_CREATED", + "TS_ONLINING", + "TS_ONLINE", + "TS_STMF_ONLINE", + "TS_DELETING_NEED_OFFLINE", + "TS_OFFLINING", + "TS_OFFLINE", + "TS_STMF_OFFLINE", + "TS_DELETING_STMF_DEREG", + "TS_DELETING_STMF_DEREG_FAIL", + "TS_DELETING", + "TS_MAX_STATE" +}; +#endif + +typedef enum { + TE_UNDEFINED = 0, + TE_STMF_ONLINE_REQ, + TE_ONLINE_SUCCESS, + TE_ONLINE_FAIL, + TE_STMF_ONLINE_COMPLETE_ACK, + TE_STMF_OFFLINE_REQ, + TE_OFFLINE_COMPLETE, + TE_STMF_OFFLINE_COMPLETE_ACK, + TE_DELETE, + TE_STMF_DEREG_SUCCESS, + TE_STMF_DEREG_FAIL, + TE_STMF_DEREG_RETRY, + TE_WAIT_REF_COMPLETE, + TE_MAX_EVENT +} iscsit_tgt_event_t; + +#ifdef ISCSIT_TGT_SM_STRINGS +static const char *iscsit_te_name[TE_MAX_EVENT+1] = { + "TE_UNDEFINED", + "TE_STMF_ONLINE_REQ", + "TE_ONLINE_SUCCESS", + "TE_ONLINE_FAIL", + "TE_STMF_ONLINE_COMPLETE_ACK", + "TE_STMF_OFFLINE_REQ", + "TE_OFFLINE_COMPLETE", + "TE_STMF_OFFLINE_COMPLETE_ACK", + "TE_DELETE", + "TE_STMF_DEREG_SUCCESS", + "TE_STMF_DEREG_FAIL", + "TE_STMF_DEREG_RETRY", + "TE_WAIT_REF_COMPLETE", + "TE_MAX_EVENT" +}; +#endif + +typedef struct { + char *target_name; + nvlist_t *target_props; + kmutex_t target_mutex; + idm_refcnt_t target_refcnt; + idm_refcnt_t target_sess_refcnt; + avl_tree_t target_tpgt_list; + avl_tree_t target_sess_list; + avl_node_t target_global_ln; + avl_node_t target_global_deleted_ln; + /* STMF lport == iSCSI target */ + scsi_devid_desc_t *target_devid; + stmf_local_port_t *target_stmf_lport; + uint8_t target_stmf_lport_registered; + + /* Target state */ + boolean_t target_sm_busy; + boolean_t target_deleting; + iscsit_tgt_state_t target_state; + iscsit_tgt_state_t target_last_state; + sm_audit_buf_t target_state_audit; + list_t target_events; + uint64_t target_generation; +} iscsit_tgt_t; + +typedef struct { + char ini_name[MAX_ISCSI_NODENAMELEN]; + nvlist_t *ini_props; + avl_node_t ini_global_ln; +} iscsit_ini_t; + +/* + * iSCSI Auth Information + */ +typedef struct conn_auth { + char ca_tgt_chapuser[iscsiAuthStringMaxLength]; + uint8_t ca_tgt_chapsecret[iscsiAuthStringMaxLength]; + int ca_tgt_chapsecretlen; + + char ca_ini_chapuser[iscsiAuthStringMaxLength]; + uint8_t ca_ini_chapsecret[iscsiAuthStringMaxLength]; + int ca_ini_chapsecretlen; + + /* RADIUS authentication information */ + boolean_t ca_use_radius; + struct sockaddr_storage ca_radius_server; + uint8_t ca_radius_secret[iscsiAuthStringMaxLength]; + int ca_radius_secretlen; + + /* authentication method list */ + iscsit_auth_method_t ca_method_valid_list[iscsiAuthMethodMaxCount]; + + /* Target alias */ + char ca_tgt_alias[MAX_ISCSI_NODENAMELEN]; +} conn_auth_t; + +/* + * We have three state machines (so far) between the IDM connection state + * machine, the session state machine, and the login state machine. All + * of these states have some concept of "full feature mode". It's going + * to be obnoxious if we use a mixture of these "ffp" representations + * since it will be difficult to ensure the three state machines + * transition at exactly the same time. We should drive decisions that + * depend on FFP from the IDM state machine which is actually snooping + * the iSCSI PDU's and will always transition at the correct time. + * + * A consequence of this approach is that there is a window just after + * login completes where we may get a SCSI request but the session + * or login state machine has not quite transitioned to "FFP". Whether + * this is a problem depends on how we use those state machines. This + * is what we should use them for: + * + * IDM Connection state machine - Decisions related to command processing + * including whether a connection is in FFP + * + * Session state machine - Summarize the state of all available connections + * for the purposes of ERL1, ERL2 and MC/S. A session in LOGGED_IN state + * should always have at least one FFP connection but there may be a brief + * window where a session in ACTIVE might have one or more FFP connections + * even though ACTIVE is not strictly an FFP state according to the RFC. + * + * Login state machine -- drive the login process, collect negotiated + * parameters. Another side effect of this approach is that we may get + * the "notify ffp" callback from the IDM connection state machine before + * the login state machine has actually transitioned to FFP state. + */ + +struct iscsit_conn_s; + +/* Update iscsit_ss_name table whenever session states are modified */ +typedef enum { + SS_UNDEFINED = 0, + SS_Q1_FREE, + SS_Q2_ACTIVE, + SS_Q3_LOGGED_IN, + SS_Q4_FAILED, + SS_Q5_CONTINUE, + SS_Q6_DONE, + SS_Q7_ERROR, + /* Add new session states above SS_MAX_STATE */ + SS_MAX_STATE +} iscsit_session_state_t; + +#ifdef ISCSIT_SESS_SM_STRINGS +/* An array of state text values, for use in logging state transitions */ +static const char *iscsit_ss_name[SS_MAX_STATE+1] = { + "SS_UNDEFINED", + "SS_Q1_FREE", + "SS_Q2_ACTIVE", + "SS_Q3_LOGGED_IN", + "SS_Q4_FAILED", + "SS_Q5_CONTINUE", + "SS_Q6_DONE", + "SS_Q7_ERROR", + "SS_MAX_STATE" +}; +#endif + +/* Update iscsit_se_name table whenever session events are modified */ +typedef enum { + SE_UNDEFINED = 0, + SE_CONN_IN_LOGIN, /* From login state machine */ + SE_CONN_LOGGED_IN, /* FFP enabled client notification */ + SE_CONN_FFP_FAIL, /* FFP disabled client notification */ + SE_CONN_FFP_DISABLE, /* FFP disabled client notification */ + SE_CONN_FAIL, /* Conn destroy client notification */ + SE_SESSION_CLOSE, /* FFP disabled client notification */ + SE_SESSION_REINSTATE, /* From login state machine */ + SE_SESSION_TIMEOUT, /* Internal */ + SE_SESSION_CONTINUE, /* From login state machine */ + SE_SESSION_CONTINUE_FAIL, /* From login state machine? */ + /* Add new events above SE_MAX_EVENT */ + SE_MAX_EVENT +} iscsit_session_event_t; + +#ifdef ISCSIT_SESS_SM_STRINGS +/* An array of event text values, for use in logging events */ +static const char *iscsit_se_name[SE_MAX_EVENT+1] = { + "SE_UNDEFINED", + "SE_CONN_IN_LOGIN", + "SE_CONN_LOGGED_IN", + "SE_CONN_FFP_FAIL", + "SE_CONN_FFP_DISABLE", + "SE_CONN_FAIL", + "SE_SESSION_CLOSE", + "SE_SESSION_REINSTATE", + "SE_SESSION_TIMEOUT", + "SE_SESSION_CONTINUE", + "SE_SESSION_CONTINUE_FAIL", + "SE_MAX_EVENT" +}; +#endif + +/* + * Set in ist_tgt after iscsit_tgt_unbind_sess to differentiate an unbound + * session from a discovery session. + */ +#define SESS_UNBOUND_FROM_TGT -1 + +typedef struct { + stmf_scsi_session_t *ist_stmf_sess; + stmf_local_port_t *ist_lport; + iscsit_tgt_t *ist_tgt; + idm_refcnt_t ist_refcnt; + kmem_cache_t *ist_task_cache; + krwlock_t ist_sn_rwlock; + kmutex_t ist_mutex; + kcondvar_t ist_cv; + iscsit_session_state_t ist_state; + iscsit_session_state_t ist_last_state; + sm_audit_buf_t ist_state_audit; + boolean_t ist_sm_busy; + boolean_t ist_sm_complete; + boolean_t ist_admin_close; + list_t ist_events; + int ist_conn_count; + int ist_ffp_conn_count; + struct iscsit_conn_s *ist_failed_conn; + timeout_id_t ist_state_timeout; + list_t ist_conn_list; + avl_node_t ist_tgt_ln; + char *ist_initiator_name; + char *ist_initiator_alias; + char *ist_target_name; + char *ist_target_alias; + uint8_t ist_isid[ISCSI_ISID_LEN]; + uint16_t ist_tsih; + uint16_t ist_tpgt_tag; + uint32_t ist_expcmdsn; + uint32_t ist_maxcmdsn; +} iscsit_sess_t; + +/* Update iscsit_ils_name table whenever login states are modified */ +typedef enum { + ILS_UNDEFINED = 0, + ILS_LOGIN_INIT, + ILS_LOGIN_WAITING, /* Waiting for more login PDU's */ + ILS_LOGIN_PROCESSING, /* Processing login request */ + ILS_LOGIN_RESPONDING, /* Sending login response */ + ILS_LOGIN_RESPONDED, /* Sent login response (no trans. to FFP) */ + ILS_LOGIN_FFP, /* Sending last login PDU for final response */ + ILS_LOGIN_DONE, /* Last login PDU sent (so we can free it) */ + ILS_LOGIN_ERROR, /* Login error, login failed */ + /* Add new login states above ILS_MAX_STATE */ + ILS_MAX_STATE +} iscsit_login_state_t; + +#ifdef ISCSIT_LOGIN_SM_STRINGS +/* An array of login state text values, for use in logging login progress */ +static const char *iscsit_ils_name[ILS_MAX_STATE+1] = { + "ILS_UNDEFINED", + "ILS_LOGIN_INIT", + "ILS_LOGIN_WAITING", + "ILS_LOGIN_PROCESSING", + "ILS_LOGIN_RESPONDING", + "ILS_LOGIN_RESPONDED", + "ILS_LOGIN_FFP", + "ILS_LOGIN_DONE", + "ILS_LOGIN_ERROR", + "ILS_MAX_STATE" +}; +#endif + +/* Update iscsit_ile_name table whenever login events are modified */ +typedef enum { + ILE_UNDEFINED = 0, + ILE_LOGIN_RCV, + ILE_LOGIN_RESP_READY, + ILE_LOGIN_FFP, + ILE_LOGIN_RESP_COMPLETE, + ILE_LOGIN_ERROR, + ILE_LOGIN_CONN_ERROR, + /* Add new login events above ILE_MAX_EVENT */ + ILE_MAX_EVENT +} iscsit_login_event_t; + +#ifdef ISCSIT_LOGIN_SM_STRINGS +/* An array of login event text values, for use in logging login events */ +static const char *iscsit_ile_name[ILE_MAX_EVENT+1] = { + "ILE_UNDEFINED", + "ILE_LOGIN_RCV", + "ILE_LOGIN_RESP_READY", + "ILE_LOGIN_FFP", + "ILE_LOGIN_RESP_COMPLETE", + "ILE_LOGIN_ERROR", + "ILE_LOGIN_CONN_ERROR", + "ILE_MAX_EVENT" +}; +#endif + +typedef struct { + uint32_t op_initial_params_set:1, + op_discovery_session:1, + op_initial_r2t:1, + op_immed_data:1, + op_data_pdu_in_order:1, + op_data_sequence_in_order:1; + uint64_t op_max_connections; + uint64_t op_max_recv_data_segment_length; + uint64_t op_max_burst_length; + uint64_t op_first_burst_length; + uint64_t op_default_time_2_wait; + uint64_t op_default_time_2_retain; + uint64_t op_max_outstanding_r2t; + uint64_t op_error_recovery_level; +} iscsit_op_params_t; + +typedef struct { + iscsit_login_state_t icl_login_state; + iscsit_login_state_t icl_login_last_state; + sm_audit_buf_t icl_state_audit; + boolean_t icl_busy; + boolean_t icl_login_complete; + kmutex_t icl_mutex; + uint32_t icl_login_itt; + uint8_t icl_login_csg; + uint8_t icl_login_nsg; + boolean_t icl_login_transit; + conn_auth_t icl_auth; + iscsit_auth_client_t icl_auth_client; + int icl_auth_pass; + list_t icl_login_events; + list_t icl_pdu_list; + uint16_t icl_tsih; + uint8_t icl_isid[ISCSI_ISID_LEN]; + uint32_t icl_cmdsn; + uint16_t icl_tpgt_tag; + char *icl_target_name; + char *icl_target_alias; + char *icl_initiator_name; + char *icl_login_resp_buf; + void *icl_login_resp_itb; /* mult-pdu idm buf */ + int icl_login_resp_len; /* For kmem_free */ + int icl_login_resp_valid_len; + uint8_t icl_login_resp_err_class; + uint8_t icl_login_resp_err_detail; + iscsi_login_rsp_hdr_t *icl_login_resp_tmpl; + idm_pdu_t *icl_login_resp; + nvlist_t *icl_request_nvlist; + nvlist_t *icl_response_nvlist; + nvlist_t *icl_negotiated_values; +} iscsit_conn_login_t; + +#define SET_LOGIN_ERROR(SLE_ICT, SLE_CLASS, SLE_DETAIL) \ + (SLE_ICT)->ict_login_sm.icl_login_resp_err_class = (SLE_CLASS); \ + (SLE_ICT)->ict_login_sm.icl_login_resp_err_detail = (SLE_DETAIL); + +typedef struct iscsit_conn_s { + idm_conn_t *ict_ic; + iscsit_sess_t *ict_sess; + kmutex_t ict_mutex; + idm_refcnt_t ict_refcnt; + idm_refcnt_t ict_dispatch_refcnt; + list_node_t ict_sess_ln; + iscsit_conn_login_t ict_login_sm; + iscsit_op_params_t ict_op; + uint16_t ict_cid; + uint32_t ict_statsn; + struct iscsit_conn_s *ict_reinstate_conn; + uint32_t ict_reinstating:1, + ict_lost:1, + ict_destroyed:1; +} iscsit_conn_t; + +#define ICT_FLAGS_DISCOVERY 0x00000001 + +typedef struct { + idm_buf_t *ibuf_idm_buf; + stmf_data_buf_t *ibuf_stmf_buf; + idm_pdu_t *ibuf_immed_data_pdu; + boolean_t ibuf_is_immed; +} iscsit_buf_t; + +typedef struct { + scsi_task_t *it_stmf_task; + idm_task_t *it_idm_task; + iscsit_buf_t *it_immed_data; + iscsit_conn_t *it_ict; + kmutex_t it_mutex; + idm_pdu_t *it_tm_pdu; + uint32_t it_stmf_abort:1, + it_aborted:1, + it_tm_task:1, + it_tm_responded:1; + uint32_t it_cmdsn; + uint32_t it_itt; + uint32_t it_ttt; +} iscsit_task_t; + +typedef struct iscsit_isns_cfg { + kmutex_t isns_mutex; + boolean_t isns_state; + list_t isns_svrs; +} iscsit_isns_cfg_t; + +/* + * State values for the iscsit service + */ +typedef enum { + ISE_UNDEFINED = 0, + ISE_DETACHED, + ISE_DISABLED, + ISE_ENABLING, + ISE_ENABLED, + ISE_BUSY, + ISE_DISABLING +} iscsit_service_enabled_t; + + +typedef struct { + iscsit_service_enabled_t global_svc_state; + dev_info_t *global_dip; + ldi_ident_t global_li; + nvlist_t *global_props; + stmf_port_provider_t *global_pp; + stmf_dbuf_store_t *global_dbuf_store; + taskq_t *global_dispatch_taskq; + idm_refcnt_t global_refcnt; + avl_tree_t global_discovery_sessions; + avl_tree_t global_target_list; + list_t global_deleted_target_list; + avl_tree_t global_tpg_list; + avl_tree_t global_ini_list; + iscsit_tpg_t *global_default_tpg; + vmem_t *global_tsih_pool; + iscsit_isns_cfg_t global_isns_cfg; + iscsi_radius_props_t global_radius_server; + krwlock_t global_rwlock; +} iscsit_global_t; + +extern iscsit_global_t iscsit_global; + +void +iscsit_global_hold(); + +void +iscsit_global_rele(); + +void +iscsit_global_wait_ref(); + +idm_status_t +iscsit_login_sm_init(iscsit_conn_t *ict); + +void +iscsit_login_sm_fini(iscsit_conn_t *ict); + +void +iscsit_login_sm_event(iscsit_conn_t *ic, iscsit_login_event_t event, + idm_pdu_t *pdu); + +void +iscsit_login_sm_event_locked(iscsit_conn_t *ic, iscsit_login_event_t event, + idm_pdu_t *pdu); + +void +iscsit_send_async_event(iscsit_conn_t *ict, uint8_t async_event); + +void +iscsit_pdu_tx(idm_pdu_t *pdu); + +/* + * IDM conn ops + */ + +idm_rx_pdu_cb_t iscsit_op_scsi_cmd; +idm_rx_pdu_cb_t iscsit_rx_pdu; +idm_rx_pdu_error_cb_t iscsit_rx_pdu_error; +idm_task_cb_t iscsit_task_aborted; +idm_client_notify_cb_t iscsit_client_notify; +idm_build_hdr_cb_t iscsit_build_hdr; + +/* + * lport entry points + */ +stmf_status_t +iscsit_xfer_scsi_data(scsi_task_t *task, stmf_data_buf_t *dbuf, + uint32_t ioflags); + +stmf_status_t +iscsit_send_scsi_status(scsi_task_t *task, uint32_t ioflags); + +void +iscsit_lport_task_free(scsi_task_t *task); + +stmf_status_t +iscsit_abort(stmf_local_port_t *lport, int abort_cmd, void *arg, + uint32_t flags); + +void +iscsit_ctl(stmf_local_port_t *lport, int cmd, void *arg); + +/* + * Connection functions + */ +idm_status_t +iscsit_conn_reinstate(iscsit_conn_t *existing_ict, iscsit_conn_t *ict); + +void +iscsit_conn_destroy_done(iscsit_conn_t *ict); + +void +iscsit_conn_set_auth(iscsit_conn_t *ict); + +void +iscsit_conn_hold(iscsit_conn_t *ict); + +void +iscsit_conn_rele(iscsit_conn_t *ict); + +/* + * Session functions + */ +int +iscsit_sess_avl_compare(const void *void_sess1, const void *void_sess2); + +iscsit_sess_t * +iscsit_sess_create(iscsit_tgt_t *tgt, iscsit_conn_t *ict, + uint32_t cmdsn, uint8_t *isid, uint16_t tag, + char *initiator_name, char *target_name, + uint8_t *error_class, uint8_t *error_detail); + +void +iscsit_sess_destroy(iscsit_sess_t *ist); + +void +iscsit_sess_hold(iscsit_sess_t *ist); + +void +iscsit_sess_rele(iscsit_sess_t *ist); + +iscsit_conn_t * +iscsit_sess_lookup_conn(iscsit_sess_t *ist, uint16_t cid); + +void +iscsit_sess_bind_conn(iscsit_sess_t *ist, iscsit_conn_t *ict); + +void +iscsit_sess_unbind_conn(iscsit_sess_t *ist, iscsit_conn_t *ict); + +void +iscsit_sess_close(iscsit_sess_t *ist); + +iscsit_sess_t * +iscsit_sess_reinstate(iscsit_tgt_t *tgt, iscsit_sess_t *ist, iscsit_conn_t *ict, + uint8_t *error_class, uint8_t *error_detail); + +void +iscsit_sess_sm_event(iscsit_sess_t *ist, iscsit_session_event_t event, + iscsit_conn_t *ict); + +/* + * Target, TPGT, TPGT and portal functions + */ + +void +iscsit_tgt_sm_event(iscsit_tgt_t *tgt, iscsit_tgt_event_t event); + +void +tgt_sm_event_locked(iscsit_tgt_t *tgt, iscsit_tgt_event_t event); + +it_cfg_status_t +iscsit_config_merge_tgt(it_config_t *cfg); + +void +iscsit_config_destroy_tgts(list_t *tgt_del_list); + +void +iscsit_config_destroy_tpgts(list_t *tpgt_del_list); + +iscsit_tgt_t * +iscsit_tgt_lookup(char *target_name); + +iscsit_tgt_t * +iscsit_tgt_lookup_locked(char *target_name); + +int +iscsit_tgt_avl_compare(const void *void_tgt1, const void *void_tgt2); + +int +iscsit_tpgt_avl_compare(const void *void_tpgt1, const void *void_tpgt2); + +void +iscsit_tgt_hold(iscsit_tgt_t *tgt); + +void +iscsit_tgt_rele(iscsit_tgt_t *tgt); + +iscsit_tpgt_t * +iscsit_tgt_lookup_tpgt(iscsit_tgt_t *tgt, uint16_t tag); + +void +iscsit_tpgt_hold(iscsit_tpgt_t *tpgt); + +void +iscsit_tpgt_rele(iscsit_tpgt_t *tpgt); + +iscsit_portal_t * +iscsit_tgt_lookup_portal(iscsit_tgt_t *tgt, struct sockaddr_storage *sa, + iscsit_tpgt_t **output_tpgt); + +iscsit_sess_t * +iscsit_tgt_lookup_sess(iscsit_tgt_t *tgt, char *initiator_name, + uint8_t *isid, uint16_t tsih, uint16_t tag); + +void +iscsit_tgt_bind_sess(iscsit_tgt_t *tgt, iscsit_sess_t *sess); + +void +iscsit_tgt_unbind_sess(iscsit_tgt_t *tgt, iscsit_sess_t *sess); + +it_cfg_status_t +iscsit_config_merge_tpg(it_config_t *cfg, list_t *tpg_del_list); + +void +iscsit_config_destroy_tpgs(list_t *tpg_del_list); + +iscsit_tpg_t * +iscsit_tpg_lookup(char *tpg_name); + +int +iscsit_tpg_avl_compare(const void *void_tpg1, const void *void_tpg2); + +void +iscsit_tpg_hold(iscsit_tpg_t *tpg); + +void +iscsit_tpg_rele(iscsit_tpg_t *tpg); + +iscsit_tpg_t * +iscsit_tpg_createdefault(); + +void +iscsit_tpg_destroydefault(iscsit_tpg_t *tpg); + +idm_status_t +iscsit_tpg_online(iscsit_tpg_t *tpg); + +void +iscsit_tpg_offline(iscsit_tpg_t *tpg); + +iscsit_portal_t * +iscsit_tpg_portal_lookup(iscsit_tpg_t *tpg, struct sockaddr_storage *sa); + +void +iscsit_portal_hold(iscsit_portal_t *portal); + +void +iscsit_portal_rele(iscsit_portal_t *portal); + +it_cfg_status_t +iscsit_config_merge_ini(it_config_t *cfg); + +int +iscsit_ini_avl_compare(const void *void_ini1, const void *void_ini2); + +iscsit_ini_t * +iscsit_ini_lookup_locked(char *ini_name); + +int +iscsit_portal_avl_compare(const void *void_portal1, const void *void_portal2); + +int +iscsit_verify_chap_resp(iscsit_conn_login_t *lsm, + unsigned int chap_i, uchar_t *chap_c, unsigned int challenge_len, + uchar_t *chap_r, unsigned int resp_len); + +#endif /* _ISCSIT_H_ */ diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.c new file mode 100644 index 000000000000..762081e5b627 --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.c @@ -0,0 +1,751 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static kv_status_t +iscsit_select_auth(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +auth_propose_chap(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_select_alg(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_recv_n(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_recv_r(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_recv_i(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_recv_c(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_auth_propose(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_auth_expect_key(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_expect_r(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +auth_chap_done(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_auth_gen_challenge(iscsit_conn_t *ict); + +static kv_status_t +iscsit_auth_gen_response(iscsit_conn_t *ict); + +typedef struct { + iscsit_auth_phase_t phase; + iscsikey_id_t kv_id; + iscsit_auth_handler_t handler; +} auth_phase_entry_t; + +/* + * This table defines all authentication phases which have valid + * handler. The entries which have a non-zero key index are for + * a key/value pair handling when a key/value is being received, + * the rest of entries are for target checking the authentication + * phase after all key/value pair(s) are handled. + */ +static const auth_phase_entry_t apet[] = { + /* by key */ + { AP_AM_UNDECIDED, KI_AUTH_METHOD, iscsit_select_auth }, + { AP_AM_PROPOSED, KI_CHAP_A, auth_propose_chap }, + + { AP_CHAP_A_WAITING, KI_CHAP_A, auth_chap_select_alg }, + { AP_CHAP_R_WAITING, KI_CHAP_N, auth_chap_recv_n }, + { AP_CHAP_R_WAITING, KI_CHAP_R, auth_chap_recv_r }, + { AP_CHAP_R_WAITING, KI_CHAP_I, auth_chap_recv_i }, + { AP_CHAP_R_WAITING, KI_CHAP_C, auth_chap_recv_c }, + { AP_CHAP_R_RCVD, KI_CHAP_N, auth_chap_recv_n }, + { AP_CHAP_R_RCVD, KI_CHAP_R, auth_chap_recv_r }, + { AP_CHAP_R_RCVD, KI_CHAP_I, auth_chap_recv_i }, + { AP_CHAP_R_RCVD, KI_CHAP_C, auth_chap_recv_c }, + + /* by target */ + { AP_AM_UNDECIDED, 0, iscsit_auth_propose }, + { AP_AM_DECIDED, 0, iscsit_auth_expect_key }, + + { AP_CHAP_A_RCVD, 0, auth_chap_expect_r }, + { AP_CHAP_R_RCVD, 0, auth_chap_done } +}; + +typedef struct { + iscsit_auth_method_t am_id; + char *am_name; +} auth_id_name_t; + +/* + * a table of mapping from the authentication index to name. + */ +static const auth_id_name_t aint[] = { + { AM_CHAP, "CHAP" }, + { AM_NONE, "None" }, + /* { AM_KRB5, "KRB5" }, */ /* Not supported */ + /* { AM_SPKM1, "SPKM1" }, */ /* Not supported */ + /* { AM_SPKM2, "SPKM2" }, */ /* Not supported */ + /* { AM_SRP, "SRP" }, */ /* Not supported */ +}; + +#define ARRAY_LENGTH(ARRAY) (sizeof (ARRAY) / sizeof (ARRAY[0])) + +/* + * get the authentication method name for the method id. + */ +static const char * +am_id_to_name(int id) +{ + int i; + const auth_id_name_t *p; + i = 0; + while (i < ARRAY_LENGTH(aint)) { + p = &(aint[i]); + if (id == p->am_id) { + return (p->am_name); + } + i ++; + } + + return (NULL); +} + +/* + * Look for an apporiate function handler which is defined for + * current authentication phase and matches the key which is + * being handled. The key index is passed in as zero when it + * is looking for an handler for checking the authentication phase + * after all security keys are handled. + */ +iscsit_auth_handler_t +iscsit_auth_get_handler(iscsit_auth_client_t *client, iscsikey_id_t kv_id) +{ + iscsit_auth_phase_t phase = client->phase; + int i; + const auth_phase_entry_t *p; + + i = 0; + p = NULL; + while (i < ARRAY_LENGTH(apet)) { + p = &(apet[i]); + if (phase == p->phase && + kv_id == p->kv_id) { + return (p->handler); + } + i ++; + } + + /* No handler can be found, it must be an invalid requst. */ + return (NULL); +} + +/* + * Select an authentication method from a list of values proposed + * by initiator. After a valid method is selected, shift the + * authentication phase to AP_AM_DECIDED. + */ +static kv_status_t +iscsit_select_auth(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + conn_auth_t *auth = &lsm->icl_auth; + iscsit_auth_method_t *am_list = &auth->ca_method_valid_list[0]; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + kv_status_t kvrc; + nvpair_t *am_choice; + char *am; + const char *am_name; + const char *text; + iscsit_auth_method_t am_id; + int i; + + client->phase = AP_AM_DECIDED; + + /* select a valid authentication method */ + am_choice = idm_get_next_listvalue(nvp, NULL); + while (am_choice != NULL) { + nvrc = nvpair_value_string(am_choice, &am); + ASSERT(nvrc == 0); + + i = 0; + am_id = am_list[i]; + while (am_id != 0) { + am_name = am_id_to_name(am_id); + if (strcasecmp(am, am_name) == 0) { + text = am; + goto am_decided; + } + i++; + am_id = am_list[i]; + } + am_choice = idm_get_next_listvalue(nvp, am_choice); + } + + /* none of authentication method is valid */ + am_id = 0; + text = ISCSI_TEXT_REJECT; + +am_decided: + client->negotiatedMethod = am_id; + /* add the selected method to the response nvlist */ + nvrc = nvlist_add_string(lsm->icl_response_nvlist, + ikvx->ik_key_name, text); + kvrc = idm_nvstat_to_kvstat(nvrc); + + return (kvrc); +} + +/* + * Initiator chooses to use CHAP after target proposed a list of + * authentication method. Set the authentication method to CHAP and + * continue on chap authentication phase. + */ +static kv_status_t +auth_propose_chap(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + + client->negotiatedMethod = AM_CHAP; + client->phase = AP_AM_DECIDED; + + return (auth_chap_select_alg(ict, nvp, ikvx)); +} + +/* + * Select a CHAP algorithm from a list of values proposed by + * initiator and shift the authentication phase to AP_CHAP_A_RCVD. + */ +static kv_status_t +auth_chap_select_alg(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc, rc; + kv_status_t kvrc; + nvpair_t *alg_choice; + char *alg_string; + uint64_t alg; + const char *text; + + client->phase = AP_CHAP_A_RCVD; + + alg_choice = idm_get_next_listvalue(nvp, NULL); + while (alg_choice != NULL) { + nvrc = nvpair_value_string(alg_choice, &alg_string); + ASSERT(nvrc == 0); + rc = idm_strtoull(alg_string, NULL, 0, (u_longlong_t *)&alg); + if (rc == 0 && alg == 5) { + /* only MD5 is supported */ + text = alg_string; + goto alg_selected; + } + + alg_choice = idm_get_next_listvalue(nvp, alg_choice); + } + + /* none of algorithm is selected */ + alg = 0; + text = ISCSI_TEXT_REJECT; + +alg_selected: + /* save the selected algorithm or zero for none is selected */ + client_set_numeric_data( + &client->recvKeyBlock, + AKT_CHAP_A, + (uint32_t)alg); + + /* add the selected algorithm to the response nvlist */ + nvrc = nvlist_add_string(lsm->icl_response_nvlist, + ikvx->ik_key_name, text); + if (alg == 0) { + kvrc = KV_AUTH_FAILED; /* No algorithm selected */ + } else { + kvrc = idm_nvstat_to_kvstat(nvrc); + if (kvrc == 0) { + kvrc = iscsit_auth_gen_challenge(ict); + } + } + + return (kvrc); +} + +/* + * Validate and save the the chap name which is sent by initiator + * and shift the authentication phase to AP_CHAP_R_RCVD. + * + * Note: the CHAP_N, CHAP_R, optionally CHAP_I and CHAP_C key/value + * pairs need to be received in one packet, we handle each of them + * separately, in order to track the authentication phase, we set + * the authentication phase to AP_CHAP_R_RCVD once one of them is + * handled. So both of AP_CHAP_R_WAITING and AP_CHAP_R_RCVD phases + * are valid for these keys. The function auth_chap_done is going + * to detect if any of these keys is missing. + */ + +/*ARGSUSED*/ +static kv_status_t +auth_chap_recv_n(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + char *chap_name; + + nvrc = nvpair_value_string(nvp, &chap_name); + ASSERT(nvrc == 0); + + client_set_string_data(&client->recvKeyBlock, + AKT_CHAP_N, + chap_name); + + client->phase = AP_CHAP_R_RCVD; + + return (KV_HANDLED); +} + +/* + * Validate and save the the chap response which is sent by initiator + * and shift the authentication phase to AP_CHAP_R_RCVD. + * + * Note: see function auth_chap_recv_n. + */ + +/*ARGSUSED*/ +static kv_status_t +auth_chap_recv_r(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + unsigned char *chap_resp; + uint_t len; + + nvrc = nvpair_value_byte_array(nvp, &chap_resp, &len); + ASSERT(nvrc == 0); + + client_set_binary_data(&client->recvKeyBlock, + AKT_CHAP_R, + chap_resp, len); + + client->phase = AP_CHAP_R_RCVD; + + return (KV_HANDLED); +} + +/* + * Validate and save the the chap identifier which is sent by initiator + * and shift the authentication phase to AP_CHAP_R_RCVD. + * + * Note: see function auth_chap_recv_n. + */ + +/*ARGSUSED*/ +static kv_status_t +auth_chap_recv_i(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + uint64_t chap_id; + + nvrc = nvpair_value_uint64(nvp, &chap_id); + ASSERT(nvrc == 0); + + client_set_numeric_data(&client->recvKeyBlock, + AKT_CHAP_I, + chap_id); + + client->phase = AP_CHAP_R_RCVD; + + return (KV_HANDLED); +} + +/* + * Validate and save the the chap challenge which is sent by initiator + * and shift the authentication phase to AP_CHAP_R_RCVD. + * + * Note: see function auth_chap_recv_n. + */ + +/*ARGSUSED*/ +static kv_status_t +auth_chap_recv_c(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + unsigned char *chap_challenge; + uint_t len; + + nvrc = nvpair_value_byte_array(nvp, &chap_challenge, &len); + ASSERT(nvrc == 0); + + client_set_binary_data( + &client->recvKeyBlock, + AKT_CHAP_C, + chap_challenge, len); + + client->phase = AP_CHAP_R_RCVD; + + return (KV_HANDLED); +} + +/* + * Shift the authentication phase to AP_CHAP_R_WAITING after target + * has successfully selected a chap algorithm. + */ + +/*ARGSUSED*/ +static kv_status_t +auth_chap_expect_r(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + + uint32_t alg; + + client_get_numeric_data(&client->recvKeyBlock, + AKT_CHAP_A, + &alg); + + if (alg != 0) { + client->phase = AP_CHAP_R_WAITING; + } else { + /* none of proposed algorithm is supported or understood. */ + client->phase = AP_CHAP_A_WAITING; + } + + return (KV_HANDLED); +} + +/* + * Initiator does not propose security negotiation, target needs to + * verify if we can bypass the security negotiation phase or propose + * a security negotiation for the initiator. + */ + +/*ARGSUSED*/ +static kv_status_t +iscsit_auth_propose(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + conn_auth_t *auth = &lsm->icl_auth; + iscsit_auth_method_t *am_list = &auth->ca_method_valid_list[0]; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + + int nvrc; + kv_status_t kvrc; + const char *am_name; + + if (am_list[0] == AM_NONE || am_list[0] == 0) { + lsm->icl_auth_pass = 1; + } + + if (lsm->icl_auth_pass == 0) { + /* + * It should be noted that the negotiation might also + * be directed by the target if the initiator does + * support security, but is not ready to direct the + * negotiation (propose options). + * - RFC3720 section 5.3.2. + */ + am_name = am_id_to_name(am_list[0]); + nvrc = nvlist_add_string( + lsm->icl_response_nvlist, + "AuthMethod", am_name); + kvrc = idm_nvstat_to_kvstat(nvrc); + + client->phase = AP_AM_PROPOSED; + } else { + kvrc = KV_HANDLED; + + client->phase = AP_DONE; + } + + return (kvrc); +} + +/* + * Shift the authentication phase according to the authentication + * method once it is selected. + */ + +/*ARGSUSED*/ +static kv_status_t +iscsit_auth_expect_key(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + + if (client->negotiatedMethod != 0) { + /* Shift security negotiation phase. */ + switch (client->negotiatedMethod) { + case AM_CHAP: + client->phase = AP_CHAP_A_WAITING; + break; + case AM_NONE: + client->phase = AP_DONE; + lsm->icl_auth_pass = 1; + break; + default: + ASSERT(0); + break; + } + } else { + /* None of proposed method is supported or understood. */ + client->phase = AP_AM_UNDECIDED; + } + + return (KV_HANDLED); +} + +/* + * The last step of the chap authentication. We will validate the + * chap parameters we received and authenticate the client here. + */ + +/*ARGSUSED*/ +static kv_status_t +auth_chap_done(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + kv_status_t kvrc = KV_HANDLED; + + conn_auth_t *auth = &lsm->icl_auth; + char *username_in; + + uint32_t chap_id; + unsigned char *chap_challenge; + unsigned int challenge_len; + char *chap_name; + unsigned char *chap_resp; + unsigned int resp_len; + + int bi_auth; + + username_in = auth->ca_ini_chapuser; + if (username_in[0] == '\0') + return (KV_AUTH_FAILED); + + /* + * Check if we have received a valid list of response keys. + */ + if (!client_auth_key_present(&client->recvKeyBlock, AKT_CHAP_N) || + !client_auth_key_present(&client->recvKeyBlock, AKT_CHAP_R) || + ((bi_auth = + client_auth_key_present(&client->recvKeyBlock, AKT_CHAP_I)) ^ + client_auth_key_present(&client->recvKeyBlock, AKT_CHAP_C))) { + return (KV_MISSING_FIELDS); + } + + client->phase = AP_DONE; + + client_get_string_data(&client->recvKeyBlock, + AKT_CHAP_N, + &chap_name); + + /* check username */ + if (strcmp(username_in, chap_name) != 0) { + return (KV_AUTH_FAILED); + } + + client_get_numeric_data(&client->sendKeyBlock, + AKT_CHAP_I, + &chap_id); + + client_get_binary_data(&client->sendKeyBlock, + AKT_CHAP_C, + &chap_challenge, &challenge_len); + + client_get_binary_data(&client->recvKeyBlock, + AKT_CHAP_R, + &chap_resp, &resp_len); + + if (iscsit_verify_chap_resp(lsm, + chap_id, chap_challenge, challenge_len, + chap_resp, resp_len) != ISCSI_AUTH_PASSED) { + return (KV_AUTH_FAILED); + } + + /* bi-direction authentication is required */ + if (bi_auth != 0) { + kvrc = iscsit_auth_gen_response(ict); + } + + lsm->icl_auth_pass = 1; + + return (kvrc); +} + +static kv_status_t +iscsit_auth_gen_challenge(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + kv_status_t kvrc; + + unsigned char idData[1]; + unsigned char *bin; + int len; + + auth_random_set_data(idData, 1); + client_set_numeric_data(&client->sendKeyBlock, + AKT_CHAP_I, + idData[0]); + + /* send chap identifier */ + nvrc = nvlist_add_uint64( + lsm->icl_response_nvlist, + "CHAP_I", idData[0]); + kvrc = idm_nvstat_to_kvstat(nvrc); + if (kvrc != 0) { + return (kvrc); + } + + bin = &(client->auth_send_binary_block.largeBinary[0]); + len = iscsiAuthChapResponseLength; + auth_random_set_data(bin, len); + client_set_binary_data(&client->sendKeyBlock, + AKT_CHAP_C, + bin, len); + + /* send chap challenge */ + nvrc = nvlist_add_byte_array( + lsm->icl_response_nvlist, + "CHAP_C", bin, len); + kvrc = idm_nvstat_to_kvstat(nvrc); + + return (kvrc); +} + +static kv_status_t +iscsit_auth_gen_response(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + int nvrc; + kv_status_t kvrc; + + conn_auth_t *auth = &lsm->icl_auth; + char *tgt_username; + uint8_t *tgt_password; + int tgt_password_length; + + uint32_t chap_id; + unsigned char *chap_challenge; + unsigned int challenge_len; + uchar_t resp[iscsiAuthChapResponseLength]; + + tgt_username = auth->ca_tgt_chapuser; + tgt_password = auth->ca_tgt_chapsecret; + tgt_password_length = auth->ca_tgt_chapsecretlen; + + /* + * We can't know in advance whether the initiator will attempt + * mutual authentication, so now we need to check whether we + * have a target CHAP secret configured. + */ + if (tgt_password_length == 0) { + return (KV_AUTH_FAILED); + } + + client_get_numeric_data(&client->recvKeyBlock, + AKT_CHAP_I, + &chap_id); + + client_get_binary_data(&client->recvKeyBlock, + AKT_CHAP_C, + &chap_challenge, &challenge_len); + + client_compute_chap_resp( + &resp[0], + chap_id, + tgt_password, tgt_password_length, + chap_challenge, challenge_len); + + nvrc = nvlist_add_string( + lsm->icl_response_nvlist, + "CHAP_N", tgt_username); + + if (nvrc == 0) { + nvrc = nvlist_add_byte_array( + lsm->icl_response_nvlist, + "CHAP_R", resp, sizeof (resp)); + } + kvrc = idm_nvstat_to_kvstat(nvrc); + + return (kvrc); +} diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.h b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.h new file mode 100644 index 000000000000..2734fd64b3b6 --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.h @@ -0,0 +1,34 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#ifndef _ISCSIT_AUTH_H_ +#define _ISCSIT_AUTH_H_ + +typedef kv_status_t (*iscsit_auth_handler_t)(iscsit_conn_t *, nvpair_t *, + const idm_kv_xlate_t *); + +iscsit_auth_handler_t +iscsit_auth_get_handler(iscsit_auth_client_t *, iscsikey_id_t); + +#endif /* _ISCSIT_AUTH_H_ */ diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.c new file mode 100644 index 000000000000..384336236876 --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.c @@ -0,0 +1,261 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +void +client_set_numeric_data(auth_key_block_t *keyBlock, + int key_type, + uint32_t numeric) +{ + auth_key_t *p; + + ASSERT(key_type < AUTH_KEY_TYPE_MAX); + + p = &keyBlock->key[key_type]; + p->value.numeric = numeric; + p->present = 1; +} + +void +client_set_string_data(auth_key_block_t *keyBlock, + int key_type, + char *string) +{ + auth_key_t *p; + + ASSERT(key_type < AUTH_KEY_TYPE_MAX); + + p = &keyBlock->key[key_type]; + p->value.string = string; + p->present = 1; +} + +void +client_set_binary_data(auth_key_block_t *keyBlock, + int key_type, + unsigned char *binary, unsigned int len) +{ + auth_key_t *p; + + ASSERT(key_type < AUTH_KEY_TYPE_MAX); + + p = &keyBlock->key[key_type]; + p->value.binary = binary; + p->len = len; + p->present = 1; +} + +void +client_get_numeric_data(auth_key_block_t *keyBlock, + int key_type, + uint32_t *numeric) +{ + auth_key_t *p; + + ASSERT(key_type < AUTH_KEY_TYPE_MAX); + + p = &keyBlock->key[key_type]; + *numeric = p->value.numeric; +} + +void +client_get_string_data(auth_key_block_t *keyBlock, + int key_type, + char **string) +{ + auth_key_t *p; + + ASSERT(key_type < AUTH_KEY_TYPE_MAX); + + p = &keyBlock->key[key_type]; + *string = p->value.string; +} + +void +client_get_binary_data(auth_key_block_t *keyBlock, + int key_type, + unsigned char **binary, unsigned int *len) +{ + auth_key_t *p; + + ASSERT(key_type < AUTH_KEY_TYPE_MAX); + + p = &keyBlock->key[key_type]; + *binary = p->value.binary; + *len = p->len; +} + +int +client_auth_key_present(auth_key_block_t *keyBlock, + int key_type) +{ + auth_key_t *p; + + ASSERT(key_type < AUTH_KEY_TYPE_MAX); + + p = &keyBlock->key[key_type]; + + return (p->present != 0 ? 1 : 0); +} + +/*ARGSUSED*/ +void +client_compute_chap_resp(uchar_t *resp, + unsigned int chap_i, + uint8_t *password, int password_len, + uchar_t *chap_c, unsigned int challenge_len) +{ + MD5_CTX context; + + MD5Init(&context); + + /* + * id byte + */ + resp[0] = (uchar_t)chap_i; + MD5Update(&context, resp, 1); + + /* + * shared secret + */ + MD5Update(&context, (uchar_t *)password, password_len); + + /* + * challenge value + */ + MD5Update(&context, chap_c, challenge_len); + + MD5Final(resp, &context); +} + +int +iscsit_verify_chap_resp(iscsit_conn_login_t *lsm, + unsigned int chap_i, + uchar_t *chap_c, unsigned int challenge_len, + uchar_t *chap_r, unsigned int resp_len) +{ + uchar_t verifyData[iscsiAuthChapResponseLength]; + conn_auth_t *auth = &lsm->icl_auth; + + /* Check if RADIUS access is enabled */ + if (auth->ca_use_radius == B_TRUE) { + chap_validation_status_type chap_valid_status; + RADIUS_CONFIG radius_cfg; + struct sockaddr_storage *sa = &auth->ca_radius_server; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + /* Use RADIUS server to authentication target */ + sin = (struct sockaddr_in *)sa; + radius_cfg.rad_svr_port = ntohs(sin->sin_port); + if (sa->ss_family == AF_INET) { + /* IPv4 */ + radius_cfg.rad_svr_addr.i_addr.in4.s_addr = + sin->sin_addr.s_addr; + radius_cfg.rad_svr_addr.i_insize = sizeof (in_addr_t); + } else if (sa->ss_family == AF_INET6) { + /* IPv6 */ + sin6 = (struct sockaddr_in6 *)sa; + bcopy(sin6->sin6_addr.s6_addr, + radius_cfg.rad_svr_addr.i_addr.in6.s6_addr, + sizeof (struct in6_addr)); + radius_cfg.rad_svr_addr.i_insize = sizeof (in6_addr_t); + } else { + return (ISCSI_AUTH_FAILED); + } + + bcopy(auth->ca_radius_secret, + radius_cfg.rad_svr_shared_secret, + MAX_RAD_SHARED_SECRET_LEN); + radius_cfg.rad_svr_shared_secret_len = + auth->ca_radius_secretlen; + + chap_valid_status = iscsit_radius_chap_validate( + auth->ca_ini_chapuser, + auth->ca_tgt_chapuser, + chap_c, + challenge_len, + chap_r, + resp_len, + chap_i, + radius_cfg.rad_svr_addr, + radius_cfg.rad_svr_port, + radius_cfg.rad_svr_shared_secret, + radius_cfg.rad_svr_shared_secret_len); + + if (chap_valid_status == CHAP_VALIDATION_PASSED) { + return (ISCSI_AUTH_PASSED); + } + return (ISCSI_AUTH_FAILED); + } + + /* Empty chap secret is not allowed */ + if (auth->ca_ini_chapsecretlen == 0) { + return (ISCSI_AUTH_FAILED); + } + + /* only MD5 is supported */ + if (resp_len != sizeof (verifyData)) { + return (ISCSI_AUTH_FAILED); + } + + client_compute_chap_resp( + &verifyData[0], + chap_i, + auth->ca_ini_chapsecret, auth->ca_ini_chapsecretlen, + chap_c, challenge_len); + + if (bcmp(chap_r, verifyData, + sizeof (verifyData)) != 0) { + return (ISCSI_AUTH_FAILED); + } + + /* chap response OK */ + return (ISCSI_AUTH_PASSED); +} + +void +auth_random_set_data(uchar_t *data, unsigned int length) +{ + (void) random_get_pseudo_bytes(data, length); +} diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.h b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.h new file mode 100644 index 000000000000..dd5a3b40d484 --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.h @@ -0,0 +1,160 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#ifndef _ISCSIT_AUTHCLIENT_H_ +#define _ISCSIT_AUTHCLIENT_H_ + +#define ISCSI_AUTH_PASSED 0 +#define ISCSI_AUTH_FAILED 1 + +enum { iscsiAuthStringMaxLength = 256 }; + +enum { AuthStringMaxLength = 256 }; +enum { AuthStringBlockMaxLength = 1024 }; +enum { AuthLargeBinaryMaxLength = 1024 }; + +enum { iscsiAuthChapResponseLength = 16 }; + +enum { iscsiAuthMethodMaxCount = 2 }; + +enum { iscsiAuthChapAlgorithmMd5 = 5 }; + +enum { + AKT_CHAP_A = 0, + AKT_CHAP_I, + AKT_CHAP_C, + AKT_CHAP_N, + AKT_CHAP_R, + AUTH_KEY_TYPE_MAX +}; + +typedef union auth_value { + uint32_t numeric; + char *string; + unsigned char *binary; +} auth_value_t; + +typedef struct auth_key { + unsigned char present; + unsigned int len; + auth_value_t value; +} auth_key_t; + +typedef struct iscsit_auth_key_block { + auth_key_t key[AUTH_KEY_TYPE_MAX]; +} auth_key_block_t; + +typedef struct auth_large_binary { + unsigned char largeBinary[AuthLargeBinaryMaxLength]; +} auth_large_binary_t; + +typedef enum { + AM_CHAP = 1, /* keep 0 as invalid */ + AM_KRB5, + AM_SPKM1, + AM_SPKM2, + AM_SRP, + AM_NONE +} iscsit_auth_method_t; + +typedef enum { + /* authentication phase start status */ + AP_AM_UNDECIDED = 0, + AP_AM_PROPOSED, + AP_AM_DECIDED, + + /* authentication phase for chap */ + AP_CHAP_A_WAITING, + AP_CHAP_A_RCVD, + AP_CHAP_R_WAITING, + AP_CHAP_R_RCVD, + + /* authentication phase for kerberos */ + AP_KRB_REQ_WAITING, + AP_KRB_REQ_RCVD, + + /* authentication phase done */ + AP_DONE +} iscsit_auth_phase_t; + +typedef struct iscsit_auth_client { + iscsit_auth_phase_t phase; + iscsit_auth_method_t negotiatedMethod; + + auth_large_binary_t auth_send_binary_block; + + auth_key_block_t recvKeyBlock; + auth_key_block_t sendKeyBlock; +} iscsit_auth_client_t; + +void +client_set_numeric_data(auth_key_block_t *keyBlock, + int key_type, + uint32_t numeric); + +void +client_set_string_data(auth_key_block_t *keyBlock, + int key_type, + char *string); + +void +client_set_binary_data(auth_key_block_t *keyBlock, + int key_type, + unsigned char *binary, unsigned int len); + +void +client_get_numeric_data(auth_key_block_t *keyBlock, + int key_type, + uint32_t *numeric); + +void +client_get_string_data(auth_key_block_t *keyBlock, + int key_type, + char **string); + +void +client_get_binary_data(auth_key_block_t *keyBlock, + int key_type, + unsigned char **binary, unsigned int *len); + +int +client_auth_key_present(auth_key_block_t *keyBlock, + int key_type); + +void +client_compute_chap_resp(uchar_t *resp, + unsigned int chap_i, + uint8_t *password, int password_len, + uchar_t *chap_c, unsigned int challenge_len); + +int +client_verify_chap_resp(char *target_chap_name, char *initiator_chap_name, + uint8_t *password, int password_len, + unsigned int chap_i, uchar_t *chap_c, unsigned int challenge_len, + uchar_t *chap_r, unsigned int resp_len); + +void +auth_random_set_data(uchar_t *data, unsigned int length); + +#endif /* _ISCSIT_AUTHCLIENT_H_ */ diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.c new file mode 100644 index 000000000000..7aec02edeff6 --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.c @@ -0,0 +1,2531 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* local defines */ +#define MAX_XID (2^16) +#define ISNS_IDLE_TIME 60 +#define MAX_RETRY (3) + +#define VALID_NAME(NAME, LEN) \ +((LEN) > 0 && (NAME)[0] != 0 && (NAME)[(LEN) - 1] == 0) + +static kmutex_t isns_mutex; +static kthread_t *isns_monitor_thr_id; + +static kcondvar_t isns_idle_cv; +static boolean_t isns_idle; + +static uint16_t xid; +#define GET_XID() atomic_inc_16_nv(&xid) + +static clock_t monitor_idle_interval; + +#define ISNS_GLOBAL_LOCK() \ + mutex_enter(&iscsit_global.global_isns_cfg.isns_mutex) + +#define ISNS_GLOBAL_LOCK_HELD() \ + MUTEX_HELD(&iscsit_global.global_isns_cfg.isns_mutex) + +#define ISNS_GLOBAL_UNLOCK() \ + mutex_exit(&iscsit_global.global_isns_cfg.isns_mutex) + +/* + * iSNS ESI thread state + */ + +static kmutex_t isns_esi_mutex; +static kcondvar_t isns_esi_cv; +static list_t esi_list; +static uint32_t isns_esi_max_interval = 0; + +/* + * List of portals. + */ + +static list_t portal_list; +static uint32_t portal_list_count = 0; + +/* How many of our portals are not "default"? */ +static uint32_t nondefault_portals = 0; + +/* + * Our entity identifier (fully-qualified hostname) + */ +static char *isns_eid = NULL; + +/* + * Our list of targets + */ +static avl_tree_t isns_target_list; + +static void +isnst_start(); + +static void +isnst_stop(); + +static void +iscsit_set_isns(boolean_t state); + +static int +iscsit_add_isns(it_portal_t *cfg_svr); + +static void +iscsit_delete_isns(iscsit_isns_svr_t *svr); + +static iscsit_isns_svr_t * +iscsit_isns_svr_lookup(struct sockaddr_storage *sa); + +static void +isnst_monitor(void *arg); + +static int +isnst_monitor_one_server(iscsit_isns_svr_t *svr, boolean_t enabled); + +static int +isnst_update_target(iscsit_tgt_t *target, isns_reg_type_t reg); + +static int +isnst_update_one_server(iscsit_isns_svr_t *svr, iscsit_tgt_t *target, + isns_reg_type_t reg); + +static int isnst_register(iscsit_isns_svr_t *svr, iscsit_tgt_t *target, + isns_reg_type_t regtype); +static int isnst_deregister(iscsit_isns_svr_t *svr, char *node); + +static size_t +isnst_make_dereg_pdu(isns_pdu_t **pdu, char *node); + +static int +isnst_verify_rsp(isns_pdu_t *pdu, isns_pdu_t *rsp); + +static uint16_t +isnst_pdu_get_op(isns_pdu_t *pdu, uint8_t **pp); + +static size_t +isnst_make_reg_pdu(isns_pdu_t **pdu, iscsit_tgt_t *target, + boolean_t svr_registered, isns_reg_type_t regtype); + +static size_t +isnst_create_pdu_header(uint16_t func_id, isns_pdu_t **pdu, uint16_t flags); + +static int +isnst_add_attr(isns_pdu_t *pdu, + size_t max_pdu_size, + uint32_t attr_id, + uint32_t attr_len, + void *attr_data, + uint32_t attr_numeric_data); + +static int +isnst_send_pdu(void *so, isns_pdu_t *pdu); + +static size_t +isnst_rcv_pdu(void *so, isns_pdu_t **pdu); + +static void * +isnst_open_so(struct sockaddr_storage *sa); + +static void +isnst_close_so(void *); + +static void +isnst_esi_thread(void *arg); + +static boolean_t +isnst_handle_esi_req(struct sonode *so, isns_pdu_t *pdu, size_t pl_size); + +static void isnst_esi_start(isns_portal_list_t *portal); +static void isnst_esi_stop(); +static void isnst_esi_stop_thread(isns_esi_tinfo_t *tinfop); +static void isnst_esi_check(); +static void isnst_esi_start_thread(isns_esi_tinfo_t *tinfop); +static isns_target_t *isnst_add_to_target_list(iscsit_tgt_t *target); +int isnst_tgt_avl_compare(const void *t1, const void *t2); +static void isnst_get_target_list(void); +static void isnst_set_server_status(iscsit_isns_svr_t *svr, + boolean_t registered); +static void isnst_wakeup_monitor(void); +static void isns_remove_portal(isns_portal_list_t *p); +static void isnst_add_default_portals(); +static int isnst_add_default_portal_attrs(isns_pdu_t *pdu, size_t pdu_size); +static void isnst_remove_default_portals(); +static boolean_t isnst_retry_registration(int rsp_status_code); + +it_cfg_status_t +isnst_config_merge(it_config_t *cfg) +{ + boolean_t new_isns_state = B_FALSE; + iscsit_isns_svr_t *isns_svr, *next_isns_svr; + it_portal_t *cfg_isns_svr; + + /* + * Determine whether iSNS is enabled in the new config. + * Isns property may not be set up yet. + */ + (void) nvlist_lookup_boolean_value(cfg->config_global_properties, + PROP_ISNS_ENABLED, &new_isns_state); + + ISNS_GLOBAL_LOCK(); + + /* Delete iSNS servers that are no longer part of the config */ + for (isns_svr = list_head(&iscsit_global.global_isns_cfg.isns_svrs); + isns_svr != NULL; + isns_svr = next_isns_svr) { + next_isns_svr = list_next( + &iscsit_global.global_isns_cfg.isns_svrs, isns_svr); + if (it_sns_svr_lookup(cfg, &isns_svr->svr_sa) == NULL) + iscsit_delete_isns(isns_svr); + } + + /* Add new iSNS servers */ + for (cfg_isns_svr = cfg->config_isns_svr_list; + cfg_isns_svr != NULL; + cfg_isns_svr = cfg_isns_svr->next) { + isns_svr = iscsit_isns_svr_lookup(&cfg_isns_svr->portal_addr); + if (isns_svr == NULL) { + if (iscsit_add_isns(cfg_isns_svr) != 0) { + /* Shouldn't happen */ + ISNS_GLOBAL_UNLOCK(); + return (ITCFG_MISC_ERR); + } + } + } + + /* Start/Stop iSNS if necessary */ + if (iscsit_global.global_isns_cfg.isns_state != new_isns_state) { + iscsit_set_isns(new_isns_state); + } + + ISNS_GLOBAL_UNLOCK(); + + /* + * There is no "modify case" since the user specifies a complete + * server list each time. A modify is the same as a remove+add. + */ + + return (0); +} + +int +iscsit_isns_init(iscsit_hostinfo_t *hostinfo) +{ + mutex_init(&iscsit_global.global_isns_cfg.isns_mutex, NULL, + MUTEX_DEFAULT, NULL); + + ISNS_GLOBAL_LOCK(); + iscsit_global.global_isns_cfg.isns_state = B_FALSE; + list_create(&iscsit_global.global_isns_cfg.isns_svrs, + sizeof (iscsit_isns_svr_t), offsetof(iscsit_isns_svr_t, svr_ln)); + list_create(&portal_list, sizeof (isns_portal_list_t), + offsetof(isns_portal_list_t, portal_ln)); + list_create(&esi_list, sizeof (isns_esi_tinfo_t), + offsetof(isns_esi_tinfo_t, esi_ln)); + portal_list_count = 0; + isns_eid = kmem_alloc(hostinfo->length, KM_SLEEP); + if (hostinfo->length > ISCSIT_MAX_HOSTNAME_LEN) + hostinfo->length = ISCSIT_MAX_HOSTNAME_LEN; + (void) strlcpy(isns_eid, hostinfo->fqhn, hostinfo->length); + avl_create(&isns_target_list, isnst_tgt_avl_compare, + sizeof (isns_target_t), offsetof(isns_target_t, target_node)); + /* + * The iscsi global lock is not held here, but it is held when + * isnst_start is called, so we need to acquire it only in this + * case. + */ + ISCSIT_GLOBAL_LOCK(RW_READER); + isnst_get_target_list(); + ISCSIT_GLOBAL_UNLOCK(); + + /* initialize isns client */ + mutex_init(&isns_mutex, NULL, MUTEX_DEFAULT, NULL); + mutex_init(&isns_esi_mutex, NULL, MUTEX_DEFAULT, NULL); + isns_monitor_thr_id = NULL; + monitor_idle_interval = ISNS_IDLE_TIME * drv_usectohz(1000000); + cv_init(&isns_idle_cv, NULL, CV_DEFAULT, NULL); + cv_init(&isns_esi_cv, NULL, CV_DEFAULT, NULL); + isns_idle = B_TRUE; + xid = 0; + ISNS_GLOBAL_UNLOCK(); + + return (0); +} + +static void +isnst_esi_stop_thread(isns_esi_tinfo_t *tinfop) +{ + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + ASSERT(mutex_owned(&isns_esi_mutex)); + + list_remove(&esi_list, tinfop); + + /* + * The only way to break a thread waiting in soaccept() is to signal + * it with EINTR. See idm_so_tgt_svc_offline for more detail. + */ + tinfop->esi_so->so_error = EINTR; + cv_signal(&tinfop->esi_so->so_connind_cv); + + /* + * Must also drop the global lock in case the esi thread is running + * and trying to update the server timestamps. + */ + mutex_exit(&isns_esi_mutex); + ISNS_GLOBAL_UNLOCK(); + thread_join(tinfop->esi_thread_did); + ISNS_GLOBAL_LOCK(); + mutex_enter(&isns_esi_mutex); + + tinfop->esi_portal->portal_esi = NULL; + kmem_free(tinfop, sizeof (isns_esi_tinfo_t)); +} + +static void +isnst_esi_stop() +{ + /* + * Basically, we just wait for all the threads to stop. They + * should already be in the process of shutting down. + */ + + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + ISNS_GLOBAL_UNLOCK(); + mutex_enter(&isns_esi_mutex); + while (!list_is_empty(&esi_list)) { + cv_wait(&isns_esi_cv, &isns_esi_mutex); + } + mutex_exit(&isns_esi_mutex); + ISNS_GLOBAL_LOCK(); +} + +void +iscsit_isns_fini() +{ + ISNS_GLOBAL_LOCK(); + iscsit_set_isns(B_FALSE); + ISNS_GLOBAL_UNLOCK(); + + mutex_enter(&isns_mutex); + while (isns_monitor_thr_id != NULL) { + cv_wait(&isns_idle_cv, &isns_mutex); + } + mutex_exit(&isns_mutex); + + ISNS_GLOBAL_LOCK(); + mutex_destroy(&isns_mutex); + cv_destroy(&isns_idle_cv); + list_destroy(&esi_list); + mutex_destroy(&isns_esi_mutex); + cv_destroy(&isns_esi_cv); + + /* + * Free our EID and target list. + */ + + if (isns_eid) { + kmem_free(isns_eid, strlen(isns_eid) + 1); + isns_eid = NULL; + } + + iscsit_global.global_isns_cfg.isns_state = B_FALSE; + avl_destroy(&isns_target_list); + list_destroy(&iscsit_global.global_isns_cfg.isns_svrs); + list_destroy(&portal_list); + portal_list_count = 0; + ISNS_GLOBAL_UNLOCK(); + + mutex_destroy(&iscsit_global.global_isns_cfg.isns_mutex); +} + +static void +iscsit_set_isns(boolean_t state) +{ + iscsit_isns_svr_t *svr; + + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + /* reset retry count for all servers */ + for (svr = list_head(&iscsit_global.global_isns_cfg.isns_svrs); + svr != NULL; + svr = list_next(&iscsit_global.global_isns_cfg.isns_svrs, svr)) { + svr->svr_retry_count = 0; + } + + /* + * Update state and isns stop flag + */ + iscsit_global.global_isns_cfg.isns_state = state; + + if (state) { + isnst_start(); + } else { + isnst_stop(); + } +} + +int +iscsit_add_isns(it_portal_t *cfg_svr) +{ + iscsit_isns_svr_t *svr; + + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + svr = kmem_zalloc(sizeof (iscsit_isns_svr_t), KM_SLEEP); + bcopy(&cfg_svr->portal_addr, &svr->svr_sa, + sizeof (struct sockaddr_storage)); + + /* put it on the global isns server list */ + list_insert_tail(&iscsit_global.global_isns_cfg.isns_svrs, svr); + + /* + * Register targets with this server if iSNS is enabled. + */ + + if (iscsit_global.global_isns_cfg.isns_state && + (isnst_update_one_server(svr, NULL, ISNS_REGISTER_ALL) == 0)) { + isnst_set_server_status(svr, B_TRUE); + } + + return (0); +} + +void +iscsit_delete_isns(iscsit_isns_svr_t *svr) +{ + boolean_t need_dereg; + + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + list_remove(&iscsit_global.global_isns_cfg.isns_svrs, svr); + + /* talk to this server if isns monitor is running */ + mutex_enter(&isns_mutex); + if (isns_monitor_thr_id != NULL) { + need_dereg = B_TRUE; + } else { + need_dereg = B_FALSE; + } + mutex_exit(&isns_mutex); + + if (need_dereg) { + (void) isnst_monitor_one_server(svr, B_FALSE); + } + + /* free the memory */ + kmem_free(svr, sizeof (*svr)); +} + +static iscsit_isns_svr_t * +iscsit_isns_svr_lookup(struct sockaddr_storage *sa) +{ + iscsit_isns_svr_t *svr; + it_portal_t portal1; + + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + bcopy(sa, &portal1.portal_addr, sizeof (struct sockaddr_storage)); + + for (svr = list_head(&iscsit_global.global_isns_cfg.isns_svrs); + svr != NULL; + svr = list_next(&iscsit_global.global_isns_cfg.isns_svrs, svr)) { + if (it_sa_compare(&svr->svr_sa, sa) == 0) + return (svr); + } + + return (NULL); +} + +int +iscsit_isns_register(iscsit_tgt_t *target) +{ + int rc = 0; + + ISNS_GLOBAL_LOCK(); + + (void) isnst_add_to_target_list(target); + + if (iscsit_global.global_isns_cfg.isns_state == B_FALSE) { + ISNS_GLOBAL_UNLOCK(); + return (rc); + } + + rc = isnst_update_target(target, ISNS_REGISTER_TARGET); + + ISNS_GLOBAL_UNLOCK(); + + return (rc); +} + +int +iscsit_isns_deregister(iscsit_tgt_t *target) +{ + void *itarget; + isns_target_t tmptgt; + iscsit_isns_svr_t *svr; + list_t *global; + + ISNS_GLOBAL_LOCK(); + + if (iscsit_global.global_isns_cfg.isns_state == B_FALSE) { + tmptgt.target = target; + + if ((itarget = avl_find(&isns_target_list, &tmptgt, NULL)) + != NULL) { + avl_remove(&isns_target_list, itarget); + kmem_free(itarget, sizeof (isns_target_t)); + } + + ISNS_GLOBAL_UNLOCK(); + return (0); + } + + /* + * Don't worry about dereg failures. + */ + (void) isnst_update_target(target, ISNS_DEREGISTER_TARGET); + + /* + * Remove the target from the list regardless of the status. + */ + + tmptgt.target = target; + if ((itarget = avl_find(&isns_target_list, &tmptgt, NULL)) != NULL) { + avl_remove(&isns_target_list, itarget); + kmem_free(itarget, sizeof (isns_target_t)); + } + + /* + * If there are no more targets, mark the server as + * unregistered. + */ + + if (avl_numnodes(&isns_target_list) == 0) { + global = &iscsit_global.global_isns_cfg.isns_svrs; + for (svr = list_head(global); svr != NULL; + svr = list_next(global, svr)) { + isnst_set_server_status(svr, B_FALSE); + } + } + + ISNS_GLOBAL_UNLOCK(); + + return (0); +} + +/* + * This function is called by iscsit when a target's configuration + * has changed. + */ + +void +iscsit_isns_target_update(iscsit_tgt_t *target) +{ + ISNS_GLOBAL_LOCK(); + + if (iscsit_global.global_isns_cfg.isns_state == B_FALSE) { + ISNS_GLOBAL_UNLOCK(); + return; + } + + (void) isnst_update_target(target, ISNS_UPDATE_TARGET); + + ISNS_GLOBAL_UNLOCK(); +} + +static void +isnst_start() +{ + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + /* + * Get target and portal lists, then start ESI threads for each portal. + */ + + isnst_get_target_list(); + isnst_add_default_portals(); + + /* + * Create a thread for monitoring server communications + */ + mutex_enter(&isns_mutex); + if (isns_monitor_thr_id == NULL) { + isns_monitor_thr_id = thread_create(NULL, 0, + isnst_monitor, NULL, 0, &p0, TS_RUN, minclsyspri); + } + mutex_exit(&isns_mutex); +} + +static void +isnst_wakeup_monitor(void) +{ + mutex_enter(&isns_mutex); + isns_idle = B_FALSE; + cv_signal(&isns_idle_cv); + mutex_exit(&isns_mutex); +} + +static void +isnst_stop() +{ + isns_target_t *itarget; + + while ((itarget = avl_first(&isns_target_list)) != NULL) { + avl_remove(&isns_target_list, itarget); + kmem_free(itarget, sizeof (isns_target_t)); + } + + isnst_remove_default_portals(); + isnst_esi_stop(); + isnst_wakeup_monitor(); +} + +/* + * isnst_update_server_timestamp + * + * When we receive an ESI request, update the timestamp for the server. + * If we don't receive one for the specified period of time, we'll attempt + * to re-register. + */ + +static void +isnst_update_server_timestamp(struct sonode *so) +{ + iscsit_isns_svr_t *svr; + struct in_addr *sin = NULL, *svr_in; + struct in6_addr *sin6 = NULL, *svr_in6; + + if (so->so_faddr_sa->sa_family == AF_INET) { + sin = &((struct sockaddr_in *) + ((void *)so->so_faddr_sa))->sin_addr; + } else { + sin6 = &((struct sockaddr_in6 *) + ((void *)so->so_faddr_sa))->sin6_addr; + } + + /* + * Find the server and update the timestamp + */ + + ISNS_GLOBAL_LOCK(); + for (svr = list_head(&iscsit_global.global_isns_cfg.isns_svrs); + svr != NULL; + svr = list_next(&iscsit_global.global_isns_cfg.isns_svrs, svr)) { + if (sin6 == NULL) { + if (svr->svr_sa.ss_family == AF_INET) { + svr_in = &((struct sockaddr_in *)&svr->svr_sa)-> + sin_addr; + if (bcmp(svr_in, sin, sizeof (in_addr_t)) + == 0) { + break; + } + } + } else { + if (svr->svr_sa.ss_family == AF_INET6) { + svr_in6 = &((struct sockaddr_in6 *) + &svr->svr_sa)->sin6_addr; + if (bcmp(svr_in6, sin6, + sizeof (in6_addr_t)) == 0) { + break; + } + } + } + } + + if (svr != NULL) { + svr->svr_last_msg = ddi_get_lbolt(); + } + ISNS_GLOBAL_UNLOCK(); +} + +/* + * isnst_monitor + * + * This function monitors registration status for each server. + */ + +/*ARGSUSED*/ +static void +isnst_monitor(void *arg) +{ + iscsit_isns_svr_t *svr; + list_t *svr_list; + boolean_t enabled; + + svr_list = &iscsit_global.global_isns_cfg.isns_svrs; + + do { + ISNS_GLOBAL_LOCK(); + enabled = iscsit_global.global_isns_cfg.isns_state; + for (svr = list_head(svr_list); svr != NULL; + svr = list_next(svr_list, svr)) { + if (isnst_monitor_one_server(svr, enabled) != 0) { + svr->svr_retry_count++; + } else { + svr->svr_retry_count = 0; + } + } + + /* + * Attempted registrations, especially to unreachable + * servers, can take time. Thus, we check the isns_state + * again here. + */ + enabled = iscsit_global.global_isns_cfg.isns_state; + ISNS_GLOBAL_UNLOCK(); + + /* + * Keep the while loop running while isns is enabled. + * If isns_idle is FALSE, we have more work to do, so + * no waiting. + */ + + mutex_enter(&isns_mutex); + + if (enabled && isns_idle) { + (void) cv_timedwait(&isns_idle_cv, &isns_mutex, + ddi_get_lbolt() + monitor_idle_interval); + } + + isns_idle = B_TRUE; + mutex_exit(&isns_mutex); + } while (enabled); + + mutex_enter(&isns_mutex); + isns_monitor_thr_id = NULL; + cv_signal(&isns_idle_cv); + mutex_exit(&isns_mutex); + + /* terminate the thread at the last */ + thread_exit(); +} + +static int +isnst_monitor_one_server(iscsit_isns_svr_t *svr, boolean_t enabled) +{ + int rc = 0; + struct sonode *so; + + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + /* + * First, take care of the case where iSNS is no longer enabled. + * + * If we're still registered, deregister. Regardless, mark the + * server as not registered. + */ + + if (enabled == B_FALSE) { + if (svr->svr_registered == B_TRUE) { + /* + * Doesn't matter if this fails. We're disabled. + */ + so = isnst_open_so(&svr->svr_sa); + if (so != NULL) { + (void) isnst_update_one_server(svr, NULL, + ISNS_DEREGISTER_ALL); + isnst_close_so(so); + } + } + + isnst_set_server_status(svr, B_FALSE); + return (0); + } + + /* + * If there are no targets, we're done. + */ + + if (avl_numnodes(&isns_target_list) == 0) { + return (0); + } + + /* + * At this point, we know iSNS is enabled. + * + * If we've received an ESI request from the server recently + * (within MAX_ESI_INTERVALS * the max interval length), + * no need to continue. + */ + + if (svr->svr_registered == B_TRUE) { + if (ddi_get_lbolt() < (svr->svr_last_msg + + drv_usectohz(isns_esi_max_interval * 1000000 * + MAX_ESI_INTERVALS))) { + return (0); + } + } else { + /* + * We're not registered... Try to register now. + */ + if ((rc = isnst_update_one_server(svr, NULL, + ISNS_REGISTER_ALL)) == 0) { + isnst_set_server_status(svr, B_TRUE); + } + } + + return (rc); +} + +static int +isnst_update_target(iscsit_tgt_t *target, isns_reg_type_t reg) +{ + iscsit_isns_svr_t *svr; + int rc = 0, curr_rc; + + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + ASSERT(iscsit_global.global_isns_cfg.isns_state == B_TRUE); + + for (svr = list_head(&iscsit_global.global_isns_cfg.isns_svrs); + svr != NULL; + svr = list_next(&iscsit_global.global_isns_cfg.isns_svrs, svr)) { + /* + * Only return success if they all succeed. Let the caller + * deal with any failure. + */ + + curr_rc = isnst_update_one_server(svr, target, reg); + + if (curr_rc == 0) { + if (reg == ISNS_REGISTER_TARGET) { + isnst_set_server_status(svr, B_TRUE); + } + } else if (rc == 0) { + rc = curr_rc; + } + } + + return (rc); +} + +static int +isnst_update_one_server(iscsit_isns_svr_t *svr, iscsit_tgt_t *target, + isns_reg_type_t reg) +{ + int rc = 0; + + switch (reg) { + case ISNS_DEREGISTER_TARGET: + rc = isnst_deregister(svr, target->target_name); + break; + + case ISNS_DEREGISTER_ALL: + rc = isnst_deregister(svr, NULL); + break; + + case ISNS_UPDATE_TARGET: + case ISNS_REGISTER_TARGET: + rc = isnst_register(svr, target, reg); + break; + + case ISNS_REGISTER_ALL: + rc = isnst_register(svr, NULL, reg); + break; + + default: + ASSERT(0); + /* NOTREACHED */ + } + + return (rc); +} + +/* + * isnst_retry_registration + * + * This function checks the return value from a registration pdu and + * determines whether or not we should retry this request. If the + * request is retried, it will do so as an "update", which means we + * re-register everything. + */ + +static boolean_t +isnst_retry_registration(int rsp_status_code) +{ + boolean_t retry; + + /* + * Currently, we will attempt to retry for "Invalid Registration", + * "Source Unauthorized", or "Busy" errors. Any other errors should + * be handled by the caller if necessary. + */ + + switch (rsp_status_code) { + case ISNS_RSP_INVALID_REGIS: + case ISNS_RSP_SRC_UNAUTHORIZED: + case ISNS_RSP_BUSY: + retry = B_TRUE; + break; + default: + retry = B_FALSE; + break; + } + + return (retry); +} + +static int +isnst_register(iscsit_isns_svr_t *svr, iscsit_tgt_t *target, + isns_reg_type_t regtype) +{ + struct sonode *so; + int rc = 0; + isns_pdu_t *pdu, *rsp; + size_t pdu_size, rsp_size; + isns_target_t *itarget, tmptgt; + boolean_t retry_reg = B_TRUE; + + /* + * Registration is a tricky thing. In order to keep things simple, + * we don't want to keep track of which targets are registered to + * which server. We rely on the target state machine to tell us + * when a target is online or offline, which prompts us to either + * register or deregister that target. + * + * When iscsit_isns_init is called, get a list of targets. Those that + * are online will need to be registered. In this case, target + * will be NULL. + * + * What this means is that if svr_registered == B_FALSE, that's + * when we'll register the network entity as well. + */ + + if ((avl_numnodes(&isns_target_list) == 0) && (target == NULL)) { + return (0); + } + + /* + * If the target is already registered and we're not doing an + * update registration, just return. + */ + + if (target != NULL) { + tmptgt.target = target; + itarget = avl_find(&isns_target_list, &tmptgt, NULL); + ASSERT(itarget); + if ((itarget->target_registered == B_TRUE) && + (regtype != ISNS_UPDATE_TARGET)) { + return (0); + } + } + + isnst_esi_check(); + + /* create TCP connection to the isns server */ + so = isnst_open_so(&svr->svr_sa); + + if (so == NULL) { + isnst_set_server_status(svr, B_FALSE); + return (-1); + } + + while (retry_reg) { + pdu_size = isnst_make_reg_pdu(&pdu, target, svr->svr_registered, + regtype); + if (pdu_size == 0) { + isnst_close_so(so); + return (-1); + } + + rc = isnst_send_pdu(so, pdu); + if (rc != 0) { + kmem_free(pdu, pdu_size); + isnst_close_so(so); + return (rc); + } + + rsp_size = isnst_rcv_pdu(so, &rsp); + if (rsp_size == 0) { + kmem_free(pdu, pdu_size); + isnst_close_so(so); + return (-1); + } + + rc = isnst_verify_rsp(pdu, rsp); + + /* + * If we got a registration error, the server may be out of + * sync. In this case, we may re-try the registration as + * a "target update", which causes us to re-register everything. + */ + + if ((retry_reg = isnst_retry_registration(rc)) == B_TRUE) { + if (regtype == ISNS_UPDATE_TARGET) { + /* + * If registration failed on an update, there + * is something terribly wrong, possibly with + * the server. + */ + rc = -1; + retry_reg = B_FALSE; + isnst_set_server_status(svr, B_FALSE); + } else { + regtype = ISNS_UPDATE_TARGET; + } + } + + kmem_free(pdu, pdu_size); + kmem_free(rsp, rsp_size); + } + + isnst_close_so(so); + + /* + * If it succeeded, mark all registered targets as such + */ + if (rc == 0) { + if ((target != NULL) && (regtype != ISNS_UPDATE_TARGET)) { + /* itarget initialized above */ + itarget->target_registered = B_TRUE; + } else { + itarget = avl_first(&isns_target_list); + while (itarget) { + itarget->target_registered = B_TRUE; + itarget = AVL_NEXT(&isns_target_list, itarget); + } + } + } + + return (rc); +} + +static isns_portal_list_t * +isns_lookup_portal(struct sockaddr_storage *p) +{ + isns_portal_list_t *portal; + + portal = list_head(&portal_list); + + while (portal != NULL) { + if (bcmp(p, &portal->portal_addr, + sizeof (struct sockaddr_storage)) == 0) { + return (portal); + } + portal = list_next(&portal_list, portal); + } + + return (NULL); +} + +static void +isns_remove_portal(isns_portal_list_t *p) +{ + list_remove(&portal_list, p); + kmem_free(p, sizeof (isns_portal_list_t)); + portal_list_count--; +} + +static isns_target_t * +isnst_add_to_target_list(iscsit_tgt_t *target) +{ + isns_target_t *itarget, tmptgt; + + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + /* + * Make sure this target isn't already in our list. If it is, + * perhaps it has just moved from offline to online. + */ + + tmptgt.target = target; + if ((itarget = (isns_target_t *)avl_find(&isns_target_list, + &tmptgt, NULL)) == NULL) { + itarget = kmem_zalloc(sizeof (isns_target_t), KM_NOSLEEP); + + /* + * If we can't get memory, we're not going to be able to + * register this target. This needs to be fixed up. + */ + if (itarget == NULL) + return (NULL); + + itarget->target = target; + avl_add(&isns_target_list, itarget); + } + + return (itarget); +} + +static int +isnst_add_default_portal_attrs(isns_pdu_t *pdu, size_t pdu_size) +{ + isns_portal_list_t *portal; + struct sockaddr_in *in; + struct sockaddr_in6 *in6; + int idx = 0; + uint32_t attr_data; + void *inaddrp; + + portal = list_head(&portal_list); + + while (portal) { + if (idx == nondefault_portals) { + break; + } + + if (portal->portal_iscsit == NULL) { + in = (struct sockaddr_in *)&portal->portal_addr; + + if (in->sin_family == AF_INET) { + attr_data = sizeof (in_addr_t); + inaddrp = (void *)&in->sin_addr; + } else if (in->sin_family == AF_INET6) { + in6 = (struct sockaddr_in6 *) + &portal->portal_addr; + attr_data = sizeof (in6_addr_t); + inaddrp = (void *)&in6->sin6_addr; + } else { + return (-1); + } + + if (isnst_add_attr(pdu, pdu_size, + ISNS_PG_PORTAL_IP_ADDR_ATTR_ID, 16, inaddrp, + attr_data) != 0) { + return (-1); + } + + /* Portal Group Portal Port */ + if (isnst_add_attr(pdu, pdu_size, + ISNS_PG_PORTAL_PORT_ATTR_ID, 4, 0, + ntohs(in->sin_port)) != 0) { + return (-1); + } + + idx++; + } + + portal = list_next(&portal_list, portal); + } + + return (0); +} + +static size_t +isnst_make_reg_pdu(isns_pdu_t **pdu, iscsit_tgt_t *target, + boolean_t svr_registered, isns_reg_type_t regtype) +{ + size_t pdu_size; + iscsit_tpgt_t *tpgt; + iscsit_tpg_t *tpg; + iscsit_portal_t *tp; + char *str; + int len; + isns_portal_list_t *portal; + isns_esi_tinfo_t *tinfop; + isns_target_t *itarget; + iscsit_tgt_t *src; + boolean_t reg_all = B_FALSE; + uint16_t flags = 0; + + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + /* + * Find a source attribute for this registration. + * + * If we're already registered, registering for the first time, or + * updating a target, we'll use the target_name of the first target + * in our list. + * + * The alternate case is that we're registering for the first time, + * but target is non-NULL. In that case, we have no targets in our + * list yet, so we use the passed in target's name. + */ + + if (svr_registered || (target == NULL) || + (regtype == ISNS_UPDATE_TARGET)) { + ASSERT(avl_numnodes(&isns_target_list) != 0); + itarget = (isns_target_t *)avl_first(&isns_target_list); + src = itarget->target; + } else { + src = target; + } + + /* + * No target means we're registering everything. A regtype of + * ISNS_UPDATE_TARGET means we're re-registering everything. + * Whether we're registering or re-registering depends on if + * we're already registered. + */ + + if ((target == NULL) || (regtype == ISNS_UPDATE_TARGET)) { + reg_all = B_TRUE; + target = src; /* This will be the 1st tgt in our list */ + + /* + * If we're already registered, this will be a replacement + * registration. In this case, we need to make sure our + * source attribute is an already registered target. + */ + if (svr_registered) { + flags = ISNS_FLAG_REPLACE_REG; + while (itarget->target_registered == B_FALSE) { + itarget = AVL_NEXT(&isns_target_list, + itarget); + } + src = itarget->target; + /* Reset itarget to the beginning of our list */ + itarget = (isns_target_t *)avl_first(&isns_target_list); + } + } + + pdu_size = isnst_create_pdu_header(ISNS_DEV_ATTR_REG, pdu, flags); + if (pdu_size == 0) { + return (0); + } + + len = strlen(src->target_name) + 1; + if (isnst_add_attr(*pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID, + len, src->target_name, 0) != 0) { + goto pdu_error; + } + + /* + * Message Key Attributes - EID + */ + len = strlen(isns_eid) + 1; + + if (isnst_add_attr(*pdu, pdu_size, ISNS_EID_ATTR_ID, + len, isns_eid, 0) != 0) { + goto pdu_error; + } + + /* Delimiter */ + if (isnst_add_attr(*pdu, pdu_size, ISNS_DELIMITER_ATTR_ID, + 0, 0, 0) != 0) { + goto pdu_error; + } + + /* + * Operating Attributes + */ + if (isnst_add_attr(*pdu, pdu_size, ISNS_EID_ATTR_ID, len, + isns_eid, 0) != 0) { + goto pdu_error; + } + + /* ENTITY Protocol - Section 6.2.2 */ + if (isnst_add_attr(*pdu, pdu_size, ISNS_ENTITY_PROTOCOL_ATTR_ID, + 4, 0, ISNS_ENTITY_PROTOCOL_ISCSI) != 0) { + goto pdu_error; + } + + /* + * Network entity portal information - only on the first registration. + */ + + if (svr_registered == B_FALSE) { + struct sockaddr_in *sin; + int addrsize; + + portal = list_head(&portal_list); + + while (portal != NULL) { + sin = (struct sockaddr_in *)&portal->portal_addr; + tinfop = portal->portal_esi; + + if (portal->portal_iscsit == NULL) { + if (sin->sin_family == AF_INET) { + addrsize = sizeof (struct in_addr); + } else { + addrsize = sizeof (struct in6_addr); + } + + /* Portal IP Address */ + if (isnst_add_attr(*pdu, pdu_size, + ISNS_PORTAL_IP_ADDR_ATTR_ID, 16, + &sin->sin_addr, addrsize) != 0) { + goto pdu_error; + } + + /* Portal Port */ + if (isnst_add_attr(*pdu, pdu_size, + ISNS_PORTAL_PORT_ATTR_ID, 4, 0, + ntohs(sin->sin_port)) != 0) { + goto pdu_error; + } + + if (tinfop && tinfop->esi_port) { + /* ESI interval and port */ + if (isnst_add_attr(*pdu, pdu_size, + ISNS_ESI_INTERVAL_ATTR_ID, 4, + NULL, 20) != 0) { + goto pdu_error; + } + + if (isnst_add_attr(*pdu, pdu_size, + ISNS_ESI_PORT_ATTR_ID, 4, NULL, + tinfop->esi_port) != 0) { + goto pdu_error; + } + } + } + + portal = list_next(&portal_list, portal); + } + } + + do { + /* Hold the target mutex */ + mutex_enter(&target->target_mutex); + + /* iSCSI Name - Section 6.4.1 */ + str = target->target_name; + len = strlen(str) + 1; + if (isnst_add_attr(*pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID, + len, str, 0) != 0) { + mutex_exit(&target->target_mutex); + goto pdu_error; + } + + /* iSCSI Node Type */ + if (isnst_add_attr(*pdu, pdu_size, + ISNS_ISCSI_NODE_TYPE_ATTR_ID, 4, 0, + ISNS_TARGET_NODE_TYPE) != 0) { + mutex_exit(&target->target_mutex); + goto pdu_error; + } + + /* iSCSI Alias */ +#if 0 + str = target->target_alias; +#else + str = "Solaris iSCSI Target"; +#endif + if (str != NULL) { + len = strlen(str) + 1; + if (isnst_add_attr(*pdu, pdu_size, + ISNS_ISCSI_ALIAS_ATTR_ID, len, str, 0) != 0) { + mutex_exit(&target->target_mutex); + goto pdu_error; + } + } + + /* for each target portal group (start)... */ + tpgt = avl_first(&target->target_tpgt_list); + ASSERT(tpgt != NULL); + do { + /* no need to explicitly register default PG */ + if ((tpgt->tpgt_tag == ISCSIT_DEFAULT_TPGT) && + (avl_numnodes(&target->target_tpgt_list) == 1)) { + tpgt = AVL_NEXT(&target->target_tpgt_list, + tpgt); + continue; + } + + tpg = tpgt->tpgt_tpg; + mutex_enter(&tpg->tpg_mutex); + + tp = avl_first(&tpg->tpg_portal_list); + + /* Portal Group Tag */ + if (isnst_add_attr(*pdu, pdu_size, + ISNS_PG_TAG_ATTR_ID, 4, 0, tpgt->tpgt_tag) != 0) { + mutex_exit(&tpg->tpg_mutex); + mutex_exit(&target->target_mutex); + goto pdu_error; + } + + ASSERT(tp != NULL); + do { + struct sockaddr_storage *ss; + struct sockaddr_in *in; + struct sockaddr_in6 *in6; + uint32_t attr_numeric_data; + void *inaddrp; + + ss = &tp->portal_addr; + in = (struct sockaddr_in *)ss; + in6 = (struct sockaddr_in6 *)ss; + + if (ss->ss_family == AF_INET) { + attr_numeric_data = sizeof (in_addr_t); + inaddrp = (void *)&in->sin_addr; + } else if (ss->ss_family == AF_INET6) { + attr_numeric_data = sizeof (in6_addr_t); + inaddrp = (void *)&in6->sin6_addr; + } else if (ss->ss_family == 0) { + /* + * Need to add all default portals + */ + attr_numeric_data = 0; + } else { + cmn_err(CE_WARN, "Unknown address " + "family for portal %p", (void *)tp); + mutex_exit(&tpg->tpg_mutex); + mutex_exit(&target->target_mutex); + goto pdu_error; + } + + if (attr_numeric_data == 0) { + if (isnst_add_default_portal_attrs(*pdu, + pdu_size) != 0) { + mutex_exit(&tpg->tpg_mutex); + mutex_exit(&target-> + target_mutex); + goto pdu_error; + } + } else { + /* Portal Group Portal IP Address */ + if (isnst_add_attr(*pdu, pdu_size, + ISNS_PG_PORTAL_IP_ADDR_ATTR_ID, 16, + inaddrp, attr_numeric_data) != 0) { + mutex_exit(&tpg->tpg_mutex); + mutex_exit(&target-> + target_mutex); + goto pdu_error; + } + + /* Portal Group Portal Port */ + if (isnst_add_attr(*pdu, pdu_size, + ISNS_PG_PORTAL_PORT_ATTR_ID, + 4, 0, ntohs(in->sin_port)) != 0) { + mutex_exit(&tpg->tpg_mutex); + mutex_exit(&target-> + target_mutex); + goto pdu_error; + } + } + + tp = AVL_NEXT(&tpg->tpg_portal_list, tp); + } while (tp != NULL); + + mutex_exit(&tpg->tpg_mutex); + tpgt = AVL_NEXT(&target->target_tpgt_list, tpgt); + } while (tpgt != NULL); + /* for each target portal group (end)... */ + + mutex_exit(&target->target_mutex); + + if (reg_all) { + itarget = AVL_NEXT(&isns_target_list, itarget); + if (itarget) { + target = itarget->target; + } else { + target = NULL; + } + } + } while ((reg_all == B_TRUE) && (target != NULL)); + + return (pdu_size); + +pdu_error: + /* packet too large, no memory */ + kmem_free(*pdu, pdu_size); + *pdu = NULL; + + return (0); +} + +static int +isnst_deregister(iscsit_isns_svr_t *svr, char *node) +{ + int rc; + isns_pdu_t *pdu, *rsp; + size_t pdu_size, rsp_size; + struct sonode *so; + + if ((svr->svr_registered == B_FALSE) || + (avl_numnodes(&isns_target_list) == 0)) { + return (0); + } + + so = isnst_open_so(&svr->svr_sa); + + if (so == NULL) { + return (-1); + } + + pdu_size = isnst_make_dereg_pdu(&pdu, node); + if (pdu_size == 0) { + isnst_close_so(so); + return (-1); + } + + rc = isnst_send_pdu(so, pdu); + if (rc != 0) { + isnst_close_so(so); + kmem_free(pdu, pdu_size); + return (rc); + } + + rsp_size = isnst_rcv_pdu(so, &rsp); + if (rsp_size == 0) { + isnst_close_so(so); + kmem_free(pdu, pdu_size); + return (-1); + } + + rc = isnst_verify_rsp(pdu, rsp); + + isnst_close_so(so); + kmem_free(pdu, pdu_size); + kmem_free(rsp, rsp_size); + + return (rc); +} + +static size_t +isnst_make_dereg_pdu(isns_pdu_t **pdu, char *node) +{ + size_t pdu_size; + int len; + isns_target_t *itarget; + iscsit_tgt_t *target; + int num_targets; + + /* + * create DevDereg Message with all of target nodes + */ + pdu_size = isnst_create_pdu_header(ISNS_DEV_DEREG, pdu, 0); + if (pdu_size == 0) { + return (0); + } + + /* + * Source attribute - Must be a storage node in the same + * network entity. We'll just grab the first one in the list. + * If it's the only online target, we turn this into a total + * deregistration regardless of the value of "node". + */ + + num_targets = avl_numnodes(&isns_target_list); + itarget = avl_first(&isns_target_list); + target = itarget->target; + + len = strlen(target->target_name) + 1; + if (isnst_add_attr(*pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID, + len, target->target_name, 0) != 0) { + goto dereg_pdu_error; + } + + /* Delimiter */ + if (isnst_add_attr(*pdu, pdu_size, ISNS_DELIMITER_ATTR_ID, + 0, 0, 0) != 0) { + goto dereg_pdu_error; + } + + /* + * Operating attributes + */ + if ((node == NULL) || (num_targets == 1)) { + /* dereg everything */ + len = strlen(isns_eid) + 1; + if (isnst_add_attr(*pdu, pdu_size, ISNS_EID_ATTR_ID, + len, isns_eid, 0) != 0) { + goto dereg_pdu_error; + } + } else { + /* dereg one target only */ + len = strlen(node) + 1; + if (isnst_add_attr(*pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID, + len, node, 0) != 0) { + goto dereg_pdu_error; + } + } + + return (pdu_size); + +dereg_pdu_error: + kmem_free(*pdu, pdu_size); + *pdu = NULL; + + return (0); +} + +static int +isnst_verify_rsp(isns_pdu_t *pdu, isns_pdu_t *rsp) +{ + uint16_t func_id; + uint16_t payload_len, rsp_payload_len; + isns_resp_t *resp; + uint8_t *pp; + isns_tlv_t *attr; + uint32_t attr_len, attr_id, esi_interval; + + /* validate response function id */ + func_id = ntohs(rsp->func_id); + switch (ntohs(pdu->func_id)) { + case ISNS_DEV_ATTR_REG: + if (func_id != ISNS_DEV_ATTR_REG_RSP) { + return (-1); + } + + /* + * Get the ESI interval returned by the server. It could + * be different than what we asked for. We never know which + * portal a request may come in on, and any server could demand + * any interval. We'll simply keep track of the largest interval + * for use in monitoring. + */ + + rsp_payload_len = isnst_pdu_get_op(rsp, &pp); + attr = (isns_tlv_t *)((void *)pp); + + while (rsp_payload_len) { + attr_len = ntohl(attr->attr_len); + attr_id = ntohl(attr->attr_id); + + if (attr_id == ISNS_ESI_INTERVAL_ATTR_ID) { + esi_interval = + ntohl(*((uint32_t *) + ((void *)(&attr->attr_value)))); + + if (esi_interval > isns_esi_max_interval) + isns_esi_max_interval = esi_interval; + + break; + } + + rsp_payload_len -= (8 + attr_len); + attr = (isns_tlv_t *) + ((void *)((uint8_t *)attr + attr_len + 8)); + } + + break; + case ISNS_DEV_DEREG: + if (func_id != ISNS_DEV_DEREG_RSP) { + return (-1); + } + break; + default: + ASSERT(0); + break; + } + + /* verify response transaction id */ + if (ntohs(rsp->xid) != ntohs(pdu->xid)) { + return (-1); + } + + /* check the error code */ + payload_len = ntohs(rsp->payload_len); + resp = (isns_resp_t *)((void *)&rsp->payload[0]); + if (payload_len < 4) { + return (-1); + } + + return (ntohl(resp->status)); +} + +static uint16_t +isnst_pdu_get_op(isns_pdu_t *pdu, uint8_t **pp) +{ + uint8_t *payload; + uint16_t payload_len; + isns_resp_t *resp; + isns_tlv_t *attr; + uint32_t attr_id; + uint32_t tlv_len; + + /* get payload */ + payload_len = ntohs(pdu->payload_len); + resp = (isns_resp_t *)((void *)&pdu->payload[0]); + + /* find the operating attributes */ + ASSERT(payload_len >= 4); + payload_len -= 4; + payload = &resp->data[0]; + + while (payload_len >= 8) { + attr = (isns_tlv_t *)((void *)payload); + tlv_len = 8 + ntohl(attr->attr_len); + if (payload_len >= tlv_len) { + payload += tlv_len; + payload_len -= tlv_len; + attr_id = ntohl(attr->attr_id); + if (attr_id == ISNS_DELIMITER_ATTR_ID) { + break; + } + } else { + /* mal-formed packet */ + payload = NULL; + payload_len = 0; + } + } + + *pp = payload; + + return (payload_len); +} + +static size_t +isnst_create_pdu_header(uint16_t func_id, isns_pdu_t **pdu, uint16_t flags) +{ + size_t pdu_size = ISNSP_MAX_PDU_SIZE; + + *pdu = (isns_pdu_t *)kmem_zalloc(pdu_size, KM_NOSLEEP); + if (*pdu != NULL) { + (*pdu)->version = htons((uint16_t)ISNSP_VERSION); + (*pdu)->func_id = htons((uint16_t)func_id); + (*pdu)->payload_len = htons(0); + (*pdu)->flags = htons(flags); + + (*pdu)->xid = htons(GET_XID()); + (*pdu)->seq = htons(0); + } else { + pdu_size = 0; + } + + return (pdu_size); +} + +static int +isnst_add_attr(isns_pdu_t *pdu, + size_t max_pdu_size, + uint32_t attr_id, + uint32_t attr_len, + void *attr_data, + uint32_t attr_numeric_data) +{ + isns_tlv_t *attr_tlv; + uint8_t *payload_ptr; + uint16_t payload_len; + uint32_t normalized_attr_len; + uint64_t attr_tlv_len; + + /* The attribute length must be 4-byte aligned. Section 5.1.3. */ + normalized_attr_len = (attr_len % 4) == 0 ? + (attr_len) : (attr_len + (4 - (attr_len % 4))); + attr_tlv_len = ISNS_TLV_ATTR_ID_LEN + + ISNS_TLV_ATTR_LEN_LEN + normalized_attr_len; + + /* Check if we are going to exceed the maximum PDU length. */ + payload_len = ntohs(pdu->payload_len); + if ((payload_len + attr_tlv_len) > max_pdu_size) { + return (1); + } + + attr_tlv = (isns_tlv_t *)kmem_zalloc(attr_tlv_len, KM_SLEEP); + + attr_tlv->attr_id = htonl(attr_id); + + switch (attr_id) { + case ISNS_DELIMITER_ATTR_ID: + break; + + case ISNS_PORTAL_IP_ADDR_ATTR_ID: + case ISNS_PG_PORTAL_IP_ADDR_ATTR_ID: + if (attr_numeric_data == sizeof (in_addr_t)) { + /* IPv4 */ + attr_tlv->attr_value[10] = 0xFF; + attr_tlv->attr_value[11] = 0xFF; + bcopy(attr_data, ((attr_tlv->attr_value) + 12), + sizeof (in_addr_t)); + } else if (attr_numeric_data == sizeof (in6_addr_t)) { + /* IPv6 */ + bcopy(attr_data, attr_tlv->attr_value, + sizeof (in6_addr_t)); + } else if (attr_numeric_data == 0) { + /* EMPTY */ + /* Do nothing */ + } else { + kmem_free(attr_tlv, attr_tlv_len); + attr_tlv = NULL; + return (1); + } + break; + + case ISNS_EID_ATTR_ID: + case ISNS_ISCSI_NAME_ATTR_ID: + case ISNS_ISCSI_ALIAS_ATTR_ID: + case ISNS_PG_ISCSI_NAME_ATTR_ID: + if (attr_len && attr_data) { + bcopy((char *)attr_data, + attr_tlv->attr_value, attr_len); + } + break; + + default: + if (attr_len == 8) { + *(uint64_t *)((void *)attr_tlv->attr_value) = + BE_64((uint64_t)attr_numeric_data); + } else if (attr_len == 4) { + *(uint32_t *)((void *)attr_tlv->attr_value) = + htonl((uint32_t)attr_numeric_data); + } + break; + } + + attr_tlv->attr_len = htonl(normalized_attr_len); + /* + * Convert the network byte ordered payload length to host byte + * ordered for local address calculation. + */ + payload_len = ntohs(pdu->payload_len); + payload_ptr = pdu->payload + payload_len; + bcopy(attr_tlv, payload_ptr, attr_tlv_len); + payload_len += attr_tlv_len; + + /* + * Convert the host byte ordered payload length back to network + * byte ordered - it's now ready to be sent on the wire. + */ + pdu->payload_len = htons(payload_len); + + kmem_free(attr_tlv, attr_tlv_len); + attr_tlv = NULL; + + return (0); +} + +static int +isnst_send_pdu(void *so, isns_pdu_t *pdu) +{ + size_t total_len, payload_len, send_len; + uint8_t *payload; + uint16_t flags, seq; + + iovec_t iov[2]; + int rc; + + /* update pdu flags */ + flags = ntohs(pdu->flags); + flags |= ISNS_FLAG_CLIENT; + flags |= ISNS_FLAG_FIRST_PDU; + + /* initalize sequence number */ + seq = 0; + + payload = pdu->payload; + + /* total payload length */ + total_len = ntohs(pdu->payload_len); + + /* fill in the pdu header */ + iov[0].iov_base = (void *)pdu; + iov[0].iov_len = ISNSP_HEADER_SIZE; + + do { + /* split the payload accordingly */ + if (total_len > ISNSP_MAX_PAYLOAD_SIZE) { + payload_len = ISNSP_MAX_PAYLOAD_SIZE; + } else { + payload_len = total_len; + /* set the last pdu flag */ + flags |= ISNS_FLAG_LAST_PDU; + } + + /* set back the pdu flags */ + pdu->flags = htons(flags); + /* set the sequence number */ + pdu->seq = htons(seq); + /* set the payload length */ + pdu->payload_len = htons(payload_len); + + /* fill in the payload */ + iov[1].iov_base = (void *)payload; + iov[1].iov_len = payload_len; + + DTRACE_PROBE3(isnst__pdu__send, uint16_t, ntohs(pdu->func_id), + uint16_t, ntohs(pdu->payload_len), caddr_t, pdu); + + /* send the pdu */ + send_len = ISNSP_HEADER_SIZE + payload_len; + rc = idm_iov_sosend(so, &iov[0], 2, send_len); + + flags &= ~ISNS_FLAG_FIRST_PDU; + payload += payload_len; + total_len -= payload_len; + + /* increase the sequence number */ + seq ++; + + } while (rc == 0 && total_len > 0); + + return (rc); +} + +static size_t +isnst_rcv_pdu(void *so, isns_pdu_t **pdu) +{ + size_t total_pdu_len; + size_t total_payload_len; + size_t payload_len; + size_t combined_len; + isns_pdu_t tmp_pdu_hdr; + isns_pdu_t *combined_pdu; + uint8_t *payload; + uint8_t *combined_payload; + + uint16_t flags; + uint16_t seq; + + *pdu = NULL; + total_pdu_len = total_payload_len = 0; + payload = NULL; + seq = 0; + + do { + /* receive the pdu header */ + if (idm_sorecv(so, &tmp_pdu_hdr, ISNSP_HEADER_SIZE) != 0 || + ntohs(tmp_pdu_hdr.seq) != seq) { + goto rcv_error; + } + + /* receive the payload */ + payload_len = ntohs(tmp_pdu_hdr.payload_len); + payload = kmem_alloc(payload_len, KM_SLEEP); + if (idm_sorecv(so, payload, payload_len) != 0) { + goto rcv_error; + } + + /* combine the pdu if it is not the first one */ + if (total_pdu_len > 0) { + combined_len = total_pdu_len + payload_len; + combined_pdu = kmem_alloc(combined_len, KM_SLEEP); + bcopy(*pdu, combined_pdu, total_pdu_len); + combined_payload = + &combined_pdu->payload[total_payload_len]; + bcopy(payload, combined_payload, payload_len); + kmem_free(*pdu, total_pdu_len); + kmem_free(payload, payload_len); + *pdu = combined_pdu; + total_payload_len += payload_len; + total_pdu_len += payload_len; + (*pdu)->payload_len = htons(total_payload_len); + } else { + total_payload_len = payload_len; + total_pdu_len = ISNSP_HEADER_SIZE + payload_len; + *pdu = kmem_alloc(total_pdu_len, KM_SLEEP); + bcopy(&tmp_pdu_hdr, *pdu, ISNSP_HEADER_SIZE); + bcopy(payload, &(*pdu)->payload[0], payload_len); + kmem_free(payload, payload_len); + } + payload = NULL; + + /* the flags of pdu which is just received */ + flags = ntohs(tmp_pdu_hdr.flags); + + /* increase sequence number by one */ + seq ++; + } while ((flags & ISNS_FLAG_LAST_PDU) == 0); + + DTRACE_PROBE3(isnst__pdu__recv, uint16_t, ntohs((*pdu)->func_id), + size_t, total_payload_len, caddr_t, *pdu); + + return (total_pdu_len); + +rcv_error: + if (*pdu != NULL) { + kmem_free(*pdu, total_pdu_len); + *pdu = NULL; + } + if (payload != NULL) { + kmem_free(payload, payload_len); + } + return (0); +} + +static void * +isnst_open_so(struct sockaddr_storage *sa) +{ + int sa_sz; + struct sonode *so; + + /* determin local IP address */ + if (sa->ss_family == AF_INET) { + /* IPv4 */ + sa_sz = sizeof (struct sockaddr_in); + + /* Create socket */ + so = idm_socreate(AF_INET, SOCK_STREAM, 0); + } else { + /* IPv6 */ + sa_sz = sizeof (struct sockaddr_in6); + + /* Create socket */ + so = idm_socreate(AF_INET6, SOCK_STREAM, 0); + } + + if (so != NULL) { + if (soconnect(so, (struct sockaddr *)sa, sa_sz, 0, 0) != 0) { + /* not calling isnst_close_so() to */ + /* make dtrace output look clear */ + idm_soshutdown(so); + idm_sodestroy(so); + so = NULL; + } + } + + if (so == NULL) { + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + char s[INET6_ADDRSTRLEN]; + void *ip; + uint16_t port; + sin = (struct sockaddr_in *)sa; + port = ntohs(sin->sin_port); + if (sa->ss_family == AF_INET) { + ip = (void *)&sin->sin_addr.s_addr; + (void) inet_ntop(AF_INET, ip, s, sizeof (s)); + } else { + sin6 = (struct sockaddr_in6 *)sa; + ip = (void *)&sin6->sin6_addr.s6_addr; + (void) inet_ntop(AF_INET6, ip, s, sizeof (s)); + } + cmn_err(CE_WARN, "open iSNS Server %s:%u failed", s, port); + } + + return (so); +} + +static void +isnst_close_so(void *so) +{ + idm_soshutdown(so); + idm_sodestroy(so); +} + + +/* + * ESI handling + */ + +static void +isnst_esi_start_thread(isns_esi_tinfo_t *tinfop) +{ + tinfop->esi_thread_running = B_FALSE; + tinfop->esi_thread_failed = B_FALSE; + tinfop->esi_registered = B_FALSE; + tinfop->esi_thread = thread_create(NULL, 0, isnst_esi_thread, + (void *)tinfop, 0, &p0, TS_RUN, minclsyspri); + + mutex_enter(&isns_esi_mutex); + list_insert_tail(&esi_list, tinfop); + + /* + * Wait for the thread to start + */ + + while (!tinfop->esi_thread_running && !tinfop->esi_thread_failed) { + cv_wait(&isns_esi_cv, &isns_esi_mutex); + } + + mutex_exit(&isns_esi_mutex); +} + +static void +isnst_esi_start(isns_portal_list_t *portal) +{ + isns_esi_tinfo_t *tinfop; + + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + /* + * Allocate our ESI thread info structure + */ + + tinfop = (isns_esi_tinfo_t *) + kmem_zalloc(sizeof (isns_esi_tinfo_t), KM_NOSLEEP); + + if (tinfop == NULL) { + cmn_err(CE_WARN, "isnst_esi_start: Cant alloc ESI"); + return; + } + + tinfop->esi_portal = portal; + portal->portal_esi = tinfop; + isnst_esi_start_thread(tinfop); +} + +/* + * isnst_esi_check + * + * Verify that all the ESI threads are running and try to restart any that + * failed for any reason. + */ + +static void +isnst_esi_check() +{ + isns_portal_list_t *portal; + isns_esi_tinfo_t *tinfop; + + /* + * Now, threads for new portals or those which stopped for some other + * reason will be started. + */ + + portal = list_head(&portal_list); + + while (portal) { + tinfop = portal->portal_esi; + + if (tinfop && (!tinfop->esi_thread_running || + tinfop->esi_thread_failed)) { + isnst_esi_start_thread(tinfop); + } + + portal = list_next(&portal_list, portal); + } +} + +/* + * isnst_esi_thread + * + * This function listens on a socket for incoming connections from an + * iSNS server until told to stop. + */ + +static void +isnst_esi_thread(void *arg) +{ + isns_esi_tinfo_t *tinfop; + struct sonode *newso; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + uint32_t on; + int rc; + isns_pdu_t *pdu; + size_t pl_size; + int family; + + tinfop = (isns_esi_tinfo_t *)arg; + tinfop->esi_thread_did = curthread->t_did; + + /* + * Create a socket to listen for requests from the iSNS server. + */ + + if (tinfop->esi_portal->portal_addr.ss_family == AF_INET) { + family = AF_INET; + } else { + family = AF_INET6; + } + + + if ((tinfop->esi_so = + idm_socreate(family, SOCK_STREAM, 0)) == NULL) { + cmn_err(CE_WARN, + "isnst_esi_thread: Unable to create socket"); + tinfop->esi_thread_failed = B_TRUE; + mutex_enter(&isns_esi_mutex); + cv_signal(&isns_esi_cv); + mutex_exit(&isns_esi_mutex); + thread_exit(); + } + + /* + * Set options, bind, and listen until we're told to stop + */ + + switch (family) { + case AF_INET: + bzero(&sin, sizeof (sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(0); + bcopy(((caddr_t)&tinfop->esi_portal->portal_addr + + offsetof(struct sockaddr_in, sin_addr)), + &sin.sin_addr.s_addr, sizeof (in_addr_t)); + on = 1; + + (void) sosetsockopt(tinfop->esi_so, SOL_SOCKET, SO_REUSEADDR, + (char *)&on, sizeof (on)); + + if (sobind(tinfop->esi_so, (struct sockaddr *)&sin, + sizeof (sin), 0, 0) != 0) { + idm_sodestroy(tinfop->esi_so); + tinfop->esi_so = NULL; + tinfop->esi_thread_failed = B_TRUE; + } else { + tinfop->esi_port = ntohs(((struct sockaddr_in *) + ((void *)tinfop->esi_so->so_laddr_sa))->sin_port); + } + + break; + + case AF_INET6: + bzero(&sin6, sizeof (sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(0); + bcopy(((caddr_t)&tinfop->esi_portal->portal_addr + + offsetof(struct sockaddr_in6, sin6_addr)), + &sin6.sin6_addr.s6_addr, sizeof (in6_addr_t)); + on = 1; + + (void) sosetsockopt(tinfop->esi_so, SOL_SOCKET, + SO_REUSEADDR, (char *)&on, sizeof (on)); + + if (sobind(tinfop->esi_so, (struct sockaddr *)&sin6, + sizeof (sin6), 0, 0) != 0) { + idm_sodestroy(tinfop->esi_so); + tinfop->esi_so = NULL; + tinfop->esi_thread_failed = B_TRUE; + } else { + tinfop->esi_port = ntohs(((struct sockaddr_in6 *) + ((void *)tinfop->esi_so->so_laddr_sa))->sin6_port); + } + + break; + } + + if (tinfop->esi_thread_failed) { + cmn_err(CE_WARN, "Unable to bind socket for ESI"); + goto esi_thread_exit; + } + + if ((rc = solisten(tinfop->esi_so, 5)) != 0) { + cmn_err(CE_WARN, "isnst_esi_thread: listen failure 0x%x", rc); + goto esi_thread_exit; + } + + mutex_enter(&isns_esi_mutex); + /* + * Mark the thread as running and the portal as no longer new. + */ + tinfop->esi_thread_running = B_TRUE; + cv_signal(&isns_esi_cv); + + while (tinfop->esi_thread_running && !tinfop->esi_thread_failed) { + mutex_exit(&isns_esi_mutex); + + if ((rc = soaccept(tinfop->esi_so, 0, &newso)) != 0) { + mutex_enter(&isns_esi_mutex); + /* + * If we were interrupted with EINTR, it's not + * really a failure. + */ + if (rc != EINTR) { + cmn_err(CE_WARN, "isnst_esi_thread: " + "accept failure (0x%x)", rc); + tinfop->esi_thread_failed = B_TRUE; + } + + tinfop->esi_thread_running = B_FALSE; + continue; + } + + mutex_enter(&isns_esi_mutex); + + pl_size = isnst_rcv_pdu(newso, &pdu); + + if (pl_size == 0) { + cmn_err(CE_WARN, "isnst_esi_thread: rcv_pdu failure"); + tinfop->esi_thread_failed = B_TRUE; + continue; + } + + if (isnst_handle_esi_req(newso, pdu, pl_size) == B_TRUE) { + tinfop->esi_registered = B_TRUE; + } + + (void) soshutdown(newso, SHUT_RDWR); + + /* + * Do not hold the esi mutex during server timestamp + * update. It requires the isns global lock, which may + * be held during other functions that also require + * the esi_mutex (potential deadlock). + */ + mutex_exit(&isns_esi_mutex); + isnst_update_server_timestamp(newso); + mutex_enter(&isns_esi_mutex); + } + mutex_exit(&isns_esi_mutex); +esi_thread_exit: + idm_soshutdown(tinfop->esi_so); + idm_sodestroy(tinfop->esi_so); + mutex_enter(&isns_esi_mutex); + tinfop->esi_thread_running = B_FALSE; + tinfop->esi_so = NULL; + tinfop->esi_port = 0; + tinfop->esi_registered = B_FALSE; + cv_signal(&isns_esi_cv); + mutex_exit(&isns_esi_mutex); + thread_exit(); +} + +/* + * Handle an incoming ESI request + */ + +static boolean_t +isnst_handle_esi_req(struct sonode *so, isns_pdu_t *pdu, size_t pl_size) +{ + isns_pdu_t *rsp_pdu; + isns_resp_t *rsp; + size_t pl_len, rsp_size; + boolean_t esirv = B_TRUE; + + if (ntohs(pdu->func_id) != ISNS_ESI) { + cmn_err(CE_WARN, "isnst_handle_esi_req: Unexpected func 0x%x", + pdu->func_id); + kmem_free(pdu, pl_size); + return (B_FALSE); + } + + pl_len = ntohs(pdu->payload_len) + 4 /* ISNS_STATUS_SZ */; + + if (pl_len > ISNSP_MAX_PAYLOAD_SIZE) { + cmn_err(CE_WARN, "isnst_handle_esi_req: PDU payload too large " + " (%ld bytes)", pl_len); + kmem_free(pdu, pl_size); + return (B_FALSE); + } + + rsp_size = isnst_create_pdu_header(ISNS_ESI_RSP, &rsp_pdu, 0); + + if (rsp_size == 0) { + cmn_err(CE_WARN, "isnst_handle_esi_req: Can't get rsp pdu"); + kmem_free(pdu, pl_size); + return (B_FALSE); + } + + rsp = (isns_resp_t *)((void *)(&rsp_pdu->payload[0])); + + /* Use xid from the request pdu */ + rsp_pdu->xid = pdu->xid; + rsp->status = htonl(ISNS_RSP_SUCCESSFUL); + + /* Copy original data */ + bcopy(pdu->payload, rsp->data, pl_len - 4); + rsp_pdu->payload_len = htons(pl_len); + + if (isnst_send_pdu(so, rsp_pdu) != 0) { + cmn_err(CE_WARN, "isnst_handle_esi_req: Send response failed"); + esirv = B_FALSE; + } + + kmem_free(rsp_pdu, rsp_size); + kmem_free(pdu, pl_size); + + return (esirv); +} + +int +isnst_tgt_avl_compare(const void *t1, const void *t2) +{ + const isns_target_t *tgt1 = t1; + const isns_target_t *tgt2 = t2; + + /* + * Sort by target (pointer to iscsit_tgt_t). + */ + + if (tgt1->target < tgt2->target) { + return (-1); + } else if (tgt1->target > tgt2->target) { + return (1); + } + + return (0); +} + +static void +isnst_get_target_list(void) +{ + iscsit_tgt_t *tgt, *next_tgt; + + /* + * Initialize our list of targets with those from the global + * list that are online. + */ + + for (tgt = avl_first(&iscsit_global.global_target_list); tgt != NULL; + tgt = next_tgt) { + next_tgt = AVL_NEXT(&iscsit_global.global_target_list, tgt); + if (tgt->target_state == TS_STMF_ONLINE) { + (void) isnst_add_to_target_list(tgt); + } + } +} + +static void +isnst_set_server_status(iscsit_isns_svr_t *svr, boolean_t registered) +{ + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + if (registered == B_TRUE) { + svr->svr_registered = B_TRUE; + svr->svr_last_msg = ddi_get_lbolt(); + } else { + svr->svr_registered = B_FALSE; + } +} + +static void +isnst_add_default_portals() +{ + idm_addr_list_t *default_portal_list; + idm_addr_t *dportal; + isns_portal_list_t *portal; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + uint32_t dpl_size, idx; + + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + dpl_size = idm_get_ipaddr(&default_portal_list); + + if (dpl_size == 0) { + cmn_err(CE_WARN, "isnst_add_default_portals: " + "No default portals"); + return; + } + + for (idx = 0; idx < default_portal_list->al_out_cnt; idx++) { + dportal = &default_portal_list->al_addrs[idx]; + + if (dportal->a_addr.i_insize == 0) { + continue; + } + + portal = kmem_zalloc(sizeof (isns_portal_list_t), KM_SLEEP); + portal->portal_iscsit = NULL; /* Default portal */ + + if (dportal->a_addr.i_insize == sizeof (struct in_addr)) { + sin = (struct sockaddr_in *)&portal->portal_addr; + sin->sin_family = AF_INET; + sin->sin_port = htons(ISCSI_LISTEN_PORT); + sin->sin_addr = dportal->a_addr.i_addr.in4; + } else { + sin6 = (struct sockaddr_in6 *)&portal->portal_addr; + sin->sin_family = AF_INET6; + sin6->sin6_port = htons(ISCSI_LISTEN_PORT); + sin6->sin6_addr = dportal->a_addr.i_addr.in6; + } + + list_insert_tail(&portal_list, portal); + isnst_esi_start(portal); + } + + kmem_free(default_portal_list, dpl_size); +} + +static void +isnst_remove_default_portals() +{ + isns_portal_list_t *portal, *next; + + ASSERT(ISNS_GLOBAL_LOCK_HELD()); + + portal = list_head(&portal_list); + + while (portal) { + next = list_next(&portal_list, portal); + + if (portal->portal_iscsit == NULL) { + mutex_enter(&isns_esi_mutex); + isnst_esi_stop_thread(portal->portal_esi); + mutex_exit(&isns_esi_mutex); + isns_remove_portal(portal); + } + + portal = next; + } +} + +/* + * These functions are called by iscsit proper when a portal comes online + * or goes offline. + */ + +void +iscsit_isns_portal_online(iscsit_portal_t *portal) +{ + isns_portal_list_t *iportal, *new_portal; + struct sockaddr_in *sin; + + ISNS_GLOBAL_LOCK(); + + iportal = isns_lookup_portal(&portal->portal_addr); + sin = (struct sockaddr_in *)&portal->portal_addr; + + /* + * If sin_family is 0, it's a "default" portal. It's possible + * sin_family may be non-zero, so check portal_iscsit. If it's NULL, + * it's a default portal as well. + */ + + if ((sin->sin_family == 0) || + (iportal && (iportal->portal_iscsit == NULL))) { + ISNS_GLOBAL_UNLOCK(); + return; + } + + ASSERT(iportal == NULL); + + new_portal = kmem_zalloc(sizeof (isns_portal_list_t), KM_SLEEP); + new_portal->portal_addr = portal->portal_addr; + sin = (struct sockaddr_in *)&new_portal->portal_addr; + new_portal->portal_iscsit = portal; + list_insert_tail(&portal_list, new_portal); + portal_list_count++; + nondefault_portals++; + + ISNS_GLOBAL_UNLOCK(); +} + +void +iscsit_isns_portal_offline(iscsit_portal_t *portal) +{ + isns_portal_list_t *iportal = NULL; + struct sockaddr_in *sin; + boolean_t default_portals = B_FALSE; + + ISNS_GLOBAL_LOCK(); + + /* + * Stop the ESI thread for this portal + */ + + iportal = isns_lookup_portal(&portal->portal_addr); + sin = (struct sockaddr_in *)&portal->portal_addr; + + if ((sin->sin_family == 0) || + (iportal && (iportal->portal_iscsit == NULL))) { + default_portals = B_TRUE; + } else { + iportal = isns_lookup_portal(&portal->portal_addr); + ASSERT(iportal); + } + + if (!default_portals) { + isns_remove_portal(iportal); + nondefault_portals--; + } + + ISNS_GLOBAL_UNLOCK(); +} diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.h b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.h new file mode 100644 index 000000000000..40c111f4912f --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.h @@ -0,0 +1,109 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#ifndef _ISNS_CLIENT_H_ +#define _ISNS_CLIENT_H_ + +#include + +/* + * List of iSNS servers with which we register. + */ + +typedef struct { + int svr_retry_count; + struct sockaddr_storage svr_sa; + clock_t svr_last_msg; + list_node_t svr_ln; + boolean_t svr_registered; +} iscsit_isns_svr_t; + +/* + * Type of registration to perform (deregister, register, update) + */ +typedef enum { + ISNS_DEREGISTER_TARGET = 0, + ISNS_DEREGISTER_ALL, + ISNS_REGISTER_TARGET, + ISNS_REGISTER_ALL, + ISNS_UPDATE_TARGET +} isns_reg_type_t; + +/* + * This structure is used to keep state with regard to the RX threads used + * for ESI. There must always be a 1:1 correspondence between the entries + * in this list and the entries in the portal_list. + */ + +struct isns_portal_list_s; + +typedef struct { + struct isns_portal_list_s *esi_portal; + kthread_t *esi_thread; + kt_did_t esi_thread_did; + struct sonode *esi_so; + uint16_t esi_port; + boolean_t esi_thread_running; + boolean_t esi_thread_failed; + boolean_t esi_registered; + boolean_t esi_not_available; + list_node_t esi_ln; +} isns_esi_tinfo_t; + +/* + * Portal list - comprised of "default" portals (i.e. idm_get_ipaddr) and + * portals that are part of target portal groups. + */ + +typedef struct isns_portal_list_s { + struct sockaddr_storage portal_addr; + isns_esi_tinfo_t *portal_esi; + iscsit_portal_t *portal_iscsit; + list_node_t portal_ln; +} isns_portal_list_t; + +typedef struct isns_target_s { + iscsit_tgt_t *target; + avl_node_t target_node; + boolean_t target_registered; +} isns_target_t; + +/* + * If no ESI request is received within this number of intervals, we'll + * try to re-register with the server. + */ +#define MAX_ESI_INTERVALS 3 + +it_cfg_status_t +isnst_config_merge(it_config_t *cfg); + +int iscsit_isns_init(iscsit_hostinfo_t *hostinfo); +void iscsit_isns_fini(); +int iscsit_isns_register(iscsit_tgt_t *target); +int iscsit_isns_deregister(iscsit_tgt_t *target); +void iscsit_isns_target_update(iscsit_tgt_t *target); +void iscsit_isns_portal_online(iscsit_portal_t *portal); +void iscsit_isns_portal_offline(iscsit_portal_t *portal); + +#endif /* _ISNS_CLIENT_H_ */ diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_login.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_login.c new file mode 100644 index 000000000000..1a24d01d18a3 --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_login.c @@ -0,0 +1,2534 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ISCSIT_LOGIN_SM_STRINGS +#include +#include + +typedef struct { + list_node_t le_ctx_node; + iscsit_login_event_t le_ctx_event; + idm_pdu_t *le_pdu; +} login_event_ctx_t; + +#ifndef TRUE +#define TRUE B_TRUE +#endif + +#ifndef FALSE +#define FALSE B_FALSE +#endif + +#define DEFAULT_RADIUS_PORT 1812 + +static void +login_sm_complete(void *ict_void); + +static void +login_sm_event_dispatch(iscsit_conn_login_t *lsm, iscsit_conn_t *ict, + login_event_ctx_t *ctx); + +static void +login_sm_init(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_waiting(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_processing(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_responding(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_responded(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_ffp(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_done(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_error(iscsit_conn_t *ict, login_event_ctx_t *ctx); + +static void +login_sm_new_state(iscsit_conn_t *ict, login_event_ctx_t *ctx, + iscsit_login_state_t new_state); + +static void +login_sm_send_ack(iscsit_conn_t *ict, idm_pdu_t *pdu); + +static idm_status_t +login_sm_validate_ack(iscsit_conn_t *ict, idm_pdu_t *pdu); + +static boolean_t +login_sm_is_last_response(iscsit_conn_t *ict); + +static void +login_sm_handle_initial_login(iscsit_conn_t *ict, idm_pdu_t *pdu); + +static void +login_sm_send_next_response(iscsit_conn_t *ict); + +static void +login_sm_process_request(iscsit_conn_t *ict); + +static idm_status_t +login_sm_req_pdu_check(iscsit_conn_t *ict, idm_pdu_t *pdu); + +static idm_status_t +login_sm_process_nvlist(iscsit_conn_t *ict); + +static idm_status_t +login_sm_check_security(iscsit_conn_t *ict); + +static void +login_sm_build_login_response(iscsit_conn_t *ict); + +static void +login_sm_ffp_actions(iscsit_conn_t *ict); + +static idm_status_t +login_sm_validate_initial_parameters(iscsit_conn_t *ict); + +static idm_status_t +login_sm_session_bind(iscsit_conn_t *ict); + +static idm_status_t +login_sm_set_auth(iscsit_conn_t *ict); + +static idm_status_t +login_sm_session_register(iscsit_conn_t *ict); + +static kv_status_t +iscsit_handle_key(iscsit_conn_t *ict, nvpair_t *nvp, char *nvp_name); + +static kv_status_t +iscsit_handle_common_key(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_handle_security_key(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_reply_security_key(iscsit_conn_t *ict); + +static kv_status_t +iscsit_handle_operational_key(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_reply_numerical(iscsit_conn_t *ict, + const char *nvp_name, const uint64_t value); + +static kv_status_t +iscsit_reply_string(iscsit_conn_t *ict, + const char *nvp_name, const char *text); + +static kv_status_t +iscsit_handle_digest(iscsit_conn_t *ict, nvpair_t *choices, + const idm_kv_xlate_t *ikvx); + +static kv_status_t +iscsit_handle_boolean(iscsit_conn_t *ict, nvpair_t *nvp, boolean_t value, + const idm_kv_xlate_t *ikvx, boolean_t iscsit_value); + +static kv_status_t +iscsit_handle_numerical(iscsit_conn_t *ict, nvpair_t *nvp, uint64_t value, + const idm_kv_xlate_t *ikvx, + uint64_t iscsi_min_value, uint64_t iscsi_max_value, + uint64_t iscsit_max_value); + +static void +iscsit_process_negotiated_values(iscsit_conn_t *ict); + +static void +login_resp_complete_cb(idm_pdu_t *pdu, idm_status_t status); + +idm_status_t +iscsit_login_sm_init(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + + bzero(lsm, sizeof (iscsit_conn_login_t)); + + /* initialize the response pdu */ + ict->ict_login_sm.icl_login_resp = + idm_pdu_alloc(sizeof (iscsi_hdr_t), 0); + if (ict->ict_login_sm.icl_login_resp == NULL) { + return (IDM_STATUS_FAIL); + } + idm_pdu_init(ict->ict_login_sm.icl_login_resp, + ict->ict_ic, ict, login_resp_complete_cb); + lsm->icl_login_resp->isp_flags |= IDM_PDU_LOGIN_TX; + + (void) nvlist_alloc(&lsm->icl_negotiated_values, NV_UNIQUE_NAME, + KM_SLEEP); + + /* + * Hold connection until the login state machine completes + */ + iscsit_conn_hold(ict); + + /* + * Pre-allocating a login response PDU means we will always be + * able to respond to a login request -- even if we can't allocate + * a data buffer to hold the text responses we can at least send + * a login failure. + */ + lsm->icl_login_resp_tmpl = kmem_zalloc(sizeof (iscsi_login_rsp_hdr_t), + KM_SLEEP); + + idm_sm_audit_init(&lsm->icl_state_audit); + mutex_init(&lsm->icl_mutex, NULL, MUTEX_DEFAULT, NULL); + list_create(&lsm->icl_login_events, sizeof (login_event_ctx_t), + offsetof(login_event_ctx_t, le_ctx_node)); + list_create(&lsm->icl_pdu_list, sizeof (idm_pdu_t), + offsetof(idm_pdu_t, isp_client_lnd)); + + lsm->icl_login_state = ILS_LOGIN_INIT; + lsm->icl_login_last_state = ILS_LOGIN_INIT; + + /* + * Initialize operational parameters to default values. Anything + * we don't specifically negotiate stays at the default. + */ + ict->ict_op.op_discovery_session = B_FALSE; + ict->ict_op.op_initial_r2t = ISCSI_DEFAULT_INITIALR2T; + ict->ict_op.op_immed_data = ISCSI_DEFAULT_IMMEDIATE_DATA; + ict->ict_op.op_data_pdu_in_order = ISCSI_DEFAULT_DATA_PDU_IN_ORDER; + ict->ict_op.op_data_sequence_in_order = + ISCSI_DEFAULT_DATA_SEQUENCE_IN_ORDER; + ict->ict_op.op_max_connections = ISCSI_DEFAULT_MAX_CONNECTIONS; + ict->ict_op.op_max_recv_data_segment_length = + ISCSI_DEFAULT_MAX_RECV_SEG_LEN; + ict->ict_op.op_max_burst_length = ISCSI_DEFAULT_MAX_BURST_LENGTH; + ict->ict_op.op_first_burst_length = ISCSI_DEFAULT_FIRST_BURST_LENGTH; + ict->ict_op.op_default_time_2_wait = ISCSI_DEFAULT_TIME_TO_WAIT; + ict->ict_op.op_default_time_2_retain = ISCSI_DEFAULT_TIME_TO_RETAIN; + ict->ict_op.op_max_outstanding_r2t = ISCSI_DEFAULT_MAX_OUT_R2T; + ict->ict_op.op_error_recovery_level = + ISCSI_DEFAULT_ERROR_RECOVERY_LEVEL; + + return (IDM_STATUS_SUCCESS); +} + +static void +login_resp_complete_cb(idm_pdu_t *pdu, idm_status_t status) +{ + iscsit_conn_t *ict = pdu->isp_private; + + ASSERT(ict->ict_login_sm.icl_login_resp == pdu); + /* + * The icl_login_resp response buffer should only ever be used + * during the LOGIN phase. + */ + ASSERT((pdu->isp_flags & IDM_PDU_LOGIN_TX) != 0); + + if ((status != IDM_STATUS_SUCCESS) || + (ict->ict_login_sm.icl_login_resp_err_class != 0)) { + iscsit_login_sm_event(ict, ILE_LOGIN_ERROR, NULL); + } else if (login_sm_is_last_response(ict) == B_TRUE) { + iscsit_login_sm_event(ict, ILE_LOGIN_RESP_COMPLETE, NULL); + } +} + +void +iscsit_login_sm_fini(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + + mutex_enter(&lsm->icl_mutex); + list_destroy(&lsm->icl_pdu_list); + list_destroy(&lsm->icl_login_events); + mutex_exit(&lsm->icl_mutex); + mutex_destroy(&lsm->icl_mutex); + + kmem_free(lsm->icl_login_resp_tmpl, sizeof (iscsi_login_rsp_hdr_t)); + idm_pdu_free(lsm->icl_login_resp); + + /* clean up the login response idm text buffer */ + if (lsm->icl_login_resp_itb != NULL) { + idm_itextbuf_free(lsm->icl_login_resp_itb); + lsm->icl_login_resp_itb = NULL; + } + + nvlist_free(lsm->icl_negotiated_values); + iscsit_conn_rele(ict); +} + +void +iscsit_login_sm_event(iscsit_conn_t *ict, iscsit_login_event_t event, + idm_pdu_t *pdu) +{ + /* + * This is a bit ugly but if we're already in ILS_LOGIN_ERROR + * or ILS_LOGIN_DONE then just drop any additional events. They + * won't change the state and it's possible we've already called + * iscsit_login_sm_fini in which case the mutex is destroyed. + */ + if ((ict->ict_login_sm.icl_login_state == ILS_LOGIN_ERROR) || + (ict->ict_login_sm.icl_login_state == ILS_LOGIN_DONE)) + return; + + mutex_enter(&ict->ict_login_sm.icl_mutex); + iscsit_login_sm_event_locked(ict, event, pdu); + mutex_exit(&ict->ict_login_sm.icl_mutex); +} +void +iscsit_login_sm_event_locked(iscsit_conn_t *ict, iscsit_login_event_t event, + idm_pdu_t *pdu) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + login_event_ctx_t *ctx; + + ctx = kmem_zalloc(sizeof (*ctx), KM_SLEEP); + + ctx->le_ctx_event = event; + ctx->le_pdu = pdu; + + list_insert_tail(&lsm->icl_login_events, ctx); + + /* + * Use the icl_busy flag to keep the state machine single threaded. + * This also serves as recursion avoidance since this flag will + * always be set if we call login_sm_event from within the + * state machine code. + */ + if (!lsm->icl_busy) { + lsm->icl_busy = B_TRUE; + while (!list_is_empty(&lsm->icl_login_events)) { + ctx = list_head(&lsm->icl_login_events); + list_remove(&lsm->icl_login_events, ctx); + idm_sm_audit_event(&lsm->icl_state_audit, + SAS_ISCSIT_LOGIN, (int)lsm->icl_login_state, + (int)ctx->le_ctx_event, (uintptr_t)pdu); + + mutex_exit(&lsm->icl_mutex); + login_sm_event_dispatch(lsm, ict, ctx); + mutex_enter(&lsm->icl_mutex); + } + lsm->icl_busy = B_FALSE; + + /* + * When the state machine reaches ILS_LOGIN_DONE or + * ILS_LOGIN_ERROR state the login process has completed + * and it's time to cleanup. The state machine code will + * mark itself "complete" when this happens. + * + * To protect against spurious events (which shouldn't + * happen) set icl_busy again. + */ + if (lsm->icl_login_complete) { + lsm->icl_busy = B_TRUE; + if (taskq_dispatch(iscsit_global.global_dispatch_taskq, + login_sm_complete, ict, DDI_SLEEP) == NULL) { + cmn_err(CE_WARN, "iscsit_login_sm_event_locked:" + " Failed to dispatch task"); + } + } + } +} + +static void +login_sm_complete(void *ict_void) +{ + iscsit_conn_t *ict = ict_void; + + /* + * State machine has run to completion, release state machine resources + */ + iscsit_login_sm_fini(ict); +} + +static void +login_sm_event_dispatch(iscsit_conn_login_t *lsm, iscsit_conn_t *ict, + login_event_ctx_t *ctx) +{ + idm_pdu_t *pdu = ctx->le_pdu; /* Only valid for some events */ + + DTRACE_PROBE2(login__event, iscsit_conn_t *, ict, + login_event_ctx_t *, ctx); + + IDM_SM_LOG(CE_NOTE, "login_sm_event_dispatch: ict %p event %s(%d)", + (void *)ict, + iscsit_ile_name[ctx->le_ctx_event], ctx->le_ctx_event); + + /* State independent actions */ + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + /* Perform basic sanity checks on the header */ + if (login_sm_req_pdu_check(ict, pdu) != IDM_STATUS_SUCCESS) { + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_INVALID_REQUEST); + /* + * If we haven't processed any PDU's yet then use + * this one as a template for the response + */ + if (ict->ict_login_sm.icl_login_resp_tmpl->opcode == 0) + login_sm_handle_initial_login(ict, pdu); + login_sm_build_login_response(ict); + login_sm_send_next_response(ict); + idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); + return; + } + break; + default: + break; + } + + /* State dependent actions */ + switch (lsm->icl_login_state) { + case ILS_LOGIN_INIT: + login_sm_init(ict, ctx); + break; + case ILS_LOGIN_WAITING: + login_sm_waiting(ict, ctx); + break; + case ILS_LOGIN_PROCESSING: + login_sm_processing(ict, ctx); + break; + case ILS_LOGIN_RESPONDING: + login_sm_responding(ict, ctx); + break; + case ILS_LOGIN_RESPONDED: + login_sm_responded(ict, ctx); + break; + case ILS_LOGIN_FFP: + login_sm_ffp(ict, ctx); + break; + case ILS_LOGIN_DONE: + login_sm_done(ict, ctx); + break; + case ILS_LOGIN_ERROR: + login_sm_error(ict, ctx); + break; + } + + kmem_free(ctx, sizeof (*ctx)); +} + +static void +login_sm_init(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + idm_pdu_t *pdu; + + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + pdu = ctx->le_pdu; + + /* + * This is the first login PDU we've received so use + * it to build the login response template and set our CSG. + */ + login_sm_handle_initial_login(ict, pdu); + + /* + * Accumulate all the login PDU's that make up this + * request on a queue. + */ + mutex_enter(&ict->ict_login_sm.icl_mutex); + list_insert_tail(&ict->ict_login_sm.icl_pdu_list, pdu); + mutex_exit(&ict->ict_login_sm.icl_mutex); + + if (pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE) { + login_sm_send_ack(ict, pdu); + login_sm_new_state(ict, ctx, ILS_LOGIN_WAITING); + } else { + login_sm_new_state(ict, ctx, ILS_LOGIN_PROCESSING); + } + break; + case ILE_LOGIN_CONN_ERROR: + case ILE_LOGIN_ERROR: + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + break; + default: + ASSERT(0); + } +} + +static void +login_sm_waiting(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + idm_pdu_t *pdu; + + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + pdu = ctx->le_pdu; + mutex_enter(&ict->ict_login_sm.icl_mutex); + list_insert_tail(&ict->ict_login_sm.icl_pdu_list, pdu); + mutex_exit(&ict->ict_login_sm.icl_mutex); + if (!(pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE)) { + login_sm_new_state(ict, ctx, ILS_LOGIN_PROCESSING); + } else { + login_sm_send_ack(ict, pdu); + } + break; + case ILE_LOGIN_ERROR: + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + break; + case ILE_LOGIN_RESP_COMPLETE: + break; + default: + ASSERT(0); + } +} + +static void +login_sm_processing(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RESP_READY: + login_sm_new_state(ict, ctx, ILS_LOGIN_RESPONDING); + break; + case ILE_LOGIN_RCV: + idm_pdu_complete(ctx->le_pdu, IDM_STATUS_SUCCESS); + /*FALLTHROUGH*/ + case ILE_LOGIN_CONN_ERROR: + case ILE_LOGIN_ERROR: + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + break; + default: + ASSERT(0); + } +} + +static void +login_sm_responding(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + idm_pdu_t *pdu; + + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + pdu = ctx->le_pdu; + /* + * We should only be in "responding" state if we have not + * sent the last PDU of a multi-PDU login response sequence. + * In that case we expect this received PDU to be an + * acknowledgement from the initiator (login PDU with C + * bit cleared and no data). If it's the acknowledgement + * we are expecting then we send the next PDU in the login + * response sequence. Otherwise it's a protocol error and + * the login fails. + */ + if (login_sm_validate_ack(ict, pdu) == IDM_STATUS_SUCCESS) { + login_sm_build_login_response(ict); + login_sm_send_next_response(ict); + } else { + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + } + idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); + break; + case ILE_LOGIN_FFP: + login_sm_new_state(ict, ctx, ILS_LOGIN_FFP); + break; + case ILE_LOGIN_RESP_COMPLETE: + login_sm_new_state(ict, ctx, ILS_LOGIN_RESPONDED); + break; + case ILE_LOGIN_CONN_ERROR: + case ILE_LOGIN_ERROR: + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + break; + default: + ASSERT(0); + } +} + +static void +login_sm_responded(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + idm_pdu_t *pdu; + iscsi_login_hdr_t *lh; + + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + pdu = ctx->le_pdu; + lh = (iscsi_login_hdr_t *)pdu->isp_hdr; + /* + * Set the CSG, NSG and Transit bits based on the this PDU. + * The CSG already validated in login_sm_req_pdu_check(). + * We'll clear the transit bit if we encounter any login + * parameters in the request that required an additional + * login transfer (i.e. no acceptable + * choices in range or we needed to change a boolean + * value from "Yes" to "No"). + */ + ict->ict_login_sm.icl_login_csg = + ISCSI_LOGIN_CURRENT_STAGE(lh->flags); + ict->ict_login_sm.icl_login_nsg = + ISCSI_LOGIN_NEXT_STAGE(lh->flags); + ict->ict_login_sm.icl_login_transit = + lh->flags & ISCSI_FLAG_LOGIN_TRANSIT; + mutex_enter(&ict->ict_login_sm.icl_mutex); + list_insert_tail(&ict->ict_login_sm.icl_pdu_list, pdu); + mutex_exit(&ict->ict_login_sm.icl_mutex); + if (pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE) { + login_sm_send_ack(ict, pdu); + login_sm_new_state(ict, ctx, ILS_LOGIN_WAITING); + } else { + login_sm_new_state(ict, ctx, ILS_LOGIN_PROCESSING); + } + break; + case ILE_LOGIN_CONN_ERROR: + case ILE_LOGIN_ERROR: + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + break; + default: + ASSERT(0); + } +} + +static void +login_sm_ffp(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RESP_COMPLETE: + login_sm_new_state(ict, ctx, ILS_LOGIN_DONE); + break; + case ILE_LOGIN_CONN_ERROR: + case ILE_LOGIN_ERROR: + login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR); + break; + default: + ASSERT(0); + } + +} + +/*ARGSUSED*/ +static void +login_sm_done(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + /* Terminal state, we should get no events */ + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + /* + * We've already processed everything we're going to + * process. Drop any additional login PDU's. + */ + idm_pdu_complete(ctx->le_pdu, IDM_STATUS_SUCCESS); + break; + case ILE_LOGIN_CONN_ERROR: + /* Don't care */ + break; + default: + ASSERT(0); + } +} + +/*ARGSUSED*/ +static void +login_sm_error(iscsit_conn_t *ict, login_event_ctx_t *ctx) +{ + switch (ctx->le_ctx_event) { + case ILE_LOGIN_RCV: + /* + * We've already processed everything we're going to + * process. Drop any additional login PDU's. + */ + idm_pdu_complete(ctx->le_pdu, IDM_STATUS_SUCCESS); + break; + case ILE_LOGIN_CONN_ERROR: + /* Don't care */ + break; + default: + ASSERT(0); + } +} + +static void +login_sm_new_state(iscsit_conn_t *ict, login_event_ctx_t *ctx, + iscsit_login_state_t new_state) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + + /* + * Validate new state + */ + ASSERT(new_state != ILS_UNDEFINED); + ASSERT3U(new_state, <, ILS_MAX_STATE); + + new_state = (new_state < ILS_MAX_STATE) ? + new_state : ILS_UNDEFINED; + + IDM_SM_LOG(CE_NOTE, "login_sm_new_state: conn %p " + "%s (%d) --> %s (%d)\n", (void *)ict->ict_ic, + iscsit_ils_name[lsm->icl_login_state], lsm->icl_login_state, + iscsit_ils_name[new_state], new_state); + + DTRACE_PROBE3(login__state__change, + iscsit_conn_t *, ict, login_event_ctx_t *, ctx, + iscsit_login_state_t, new_state); + + mutex_enter(&lsm->icl_mutex); + idm_sm_audit_state_change(&lsm->icl_state_audit, SAS_ISCSIT_LOGIN, + (int)lsm->icl_login_state, (int)new_state); + lsm->icl_login_last_state = lsm->icl_login_state; + lsm->icl_login_state = new_state; + mutex_exit(&lsm->icl_mutex); + + switch (lsm->icl_login_state) { + case ILS_LOGIN_WAITING: + /* Do nothing, waiting for more login PDU's */ + break; + case ILS_LOGIN_PROCESSING: + /* All login PDU's received, process login request */ + login_sm_process_request(ict); + break; + case ILS_LOGIN_RESPONDING: + login_sm_send_next_response(ict); + break; + case ILS_LOGIN_RESPONDED: + /* clean up the login response idm text buffer */ + if (lsm->icl_login_resp_itb != NULL) { + idm_itextbuf_free(lsm->icl_login_resp_itb); + lsm->icl_login_resp_itb = NULL; + } + break; + case ILS_LOGIN_FFP: + login_sm_ffp_actions(ict); + break; + case ILS_LOGIN_DONE: + case ILS_LOGIN_ERROR: + /* Free login SM resources */ + lsm->icl_login_complete = B_TRUE; + break; + case ILS_LOGIN_INIT: /* Initial state, can't return */ + default: + ASSERT(0); + /*NOTREACHED*/ + } +} + +/*ARGSUSED*/ +static void +login_sm_send_ack(iscsit_conn_t *ict, idm_pdu_t *pdu) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + + ASSERT((lsm->icl_login_resp->isp_flags & IDM_PDU_LOGIN_TX) != 0); + bcopy(lsm->icl_login_resp_tmpl, + lsm->icl_login_resp->isp_hdr, sizeof (iscsi_hdr_t)); + idm_pdu_tx(lsm->icl_login_resp); +} + +/*ARGSUSED*/ +static idm_status_t +login_sm_validate_ack(iscsit_conn_t *ict, idm_pdu_t *pdu) +{ + iscsi_hdr_t *ihp = pdu->isp_hdr; + if (ihp->flags & ISCSI_FLAG_TEXT_CONTINUE) { + return (IDM_STATUS_FAIL); + } + if (ntoh24(ihp->dlength) != 0) { + return (IDM_STATUS_FAIL); + } + return (IDM_STATUS_SUCCESS); +} + +static boolean_t +login_sm_is_last_response(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + + if (lsm->icl_login_resp->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE) { + return (B_FALSE); + } + return (B_TRUE); +} + + +static void +login_sm_handle_initial_login(iscsit_conn_t *ict, idm_pdu_t *pdu) +{ + iscsi_login_hdr_t *lh_req = (iscsi_login_hdr_t *)pdu->isp_hdr; + iscsi_login_rsp_hdr_t *lh_resp = + ict->ict_login_sm.icl_login_resp_tmpl; + + /* + * First login PDU, this connection should not have a sesssion + * associated. + */ + ASSERT(ict->ict_sess == NULL); + + /* + * Save off TSIH and ISID for later use in finding a session + */ + ict->ict_login_sm.icl_cmdsn = ntohl(lh_req->cmdsn); + ict->ict_login_sm.icl_tsih = ntohs(lh_req->tsid); + bcopy(lh_req->isid, ict->ict_login_sm.icl_isid, ISCSI_ISID_LEN); + + /* + * We'll need the CID as well + */ + ict->ict_cid = ntohs(lh_req->cid); + + /* + * Set the CSG, NSG and Transit bits based on the first PDU + * in the login sequence. The CSG already validated in + * login_sm_req_pdu_check(). We'll clear the transit bit if + * we encounter any login parameters in the request that + * required an additional login transfer (i.e. no acceptable + * choices in range or we needed to change a boolean + * value from "Yes" to "No"). + */ + ict->ict_login_sm.icl_login_csg = + ISCSI_LOGIN_CURRENT_STAGE(lh_req->flags); + ict->ict_login_sm.icl_login_nsg = + ISCSI_LOGIN_NEXT_STAGE(lh_req->flags); + ict->ict_login_sm.icl_login_transit = + lh_req->flags & ISCSI_FLAG_LOGIN_TRANSIT; + + /* + * Initialize header for login reject response. This will also + * be copied for use as a template for other login responses + */ + lh_resp->opcode = ISCSI_OP_LOGIN_RSP; + lh_resp->max_version = ISCSIT_MAX_VERSION; + + /* + * We already validated that we can support one of the initiator's + * versions in login_sm_req_pdu_check(). + */ +#if (ISCSIT_MAX_VERSION > 0) + if (ISCSIT_MAX_VERSION >= lh_req->min_version) { + lh_resp->active_version = + MIN(lh_req->max_version, ISCSIT_MAX_VERSION); + } else { + ASSERT(ISCSIT_MAX_VERSION <= lh_req->max_version); + lh_resp->active_version = ISCSIT_MAX_VERSION; + } +#endif + + lh_resp->hlength = 0; /* No AHS */ + bcopy(lh_req->isid, lh_resp->isid, ISCSI_ISID_LEN); + lh_resp->tsid = lh_req->tsid; + lh_resp->itt = lh_req->itt; + + /* + * StatSn, ExpCmdSn and MaxCmdSn will be set immediately before + * transmission + */ +} + +static void +login_sm_send_next_response(iscsit_conn_t *ict) +{ + idm_pdu_t *pdu = ict->ict_login_sm.icl_login_resp; + iscsi_login_rsp_hdr_t *lh_resp = (iscsi_login_rsp_hdr_t *)pdu->isp_hdr; + + /* Tell the IDM layer this PDU is part of the login phase */ + ASSERT((pdu->isp_flags & IDM_PDU_LOGIN_TX) != 0); + + /* + * Fill in header values + */ + hton24(lh_resp->dlength, pdu->isp_datalen); + + /* + * If this is going to be the last PDU of a login response + * that moves us to FFP then generate the ILE_LOGIN_FFP event. + */ + if (lh_resp->status_class == ISCSI_STATUS_CLASS_SUCCESS) { + ASSERT(ict->ict_sess != NULL); + + if ((lh_resp->flags & ISCSI_FLAG_LOGIN_TRANSIT) && + (ISCSI_LOGIN_NEXT_STAGE(lh_resp->flags) == + ISCSI_FULL_FEATURE_PHASE) && + !(lh_resp->flags & ISCSI_FLAG_LOGIN_CONTINUE)) { + iscsit_login_sm_event_locked(ict, ILE_LOGIN_FFP, NULL); + } + + iscsit_pdu_tx(pdu); + } else { + /* + * If status_class != ISCSI_STATUS_CLASS_SUCCESS then + * StatSN is not valid and we can call idm_pdu_tx instead + * of iscsit_pdu_tx. This is very good thing since in + * some cases of login failure we may not have a session. + * Since iscsit_calc_rspsn grabs the session mutex while + * it is retrieving values for expcmdsn and maxcmdsn this + * would cause a panic. + * + * Since we still want a value for expcmdsn, fill in an + * appropriate value based on the login request before + * sending the response. + */ + lh_resp->expcmdsn = htonl(ict->ict_login_sm.icl_cmdsn + 1); + lh_resp->maxcmdsn = htonl(ict->ict_login_sm.icl_cmdsn + 2); + + idm_pdu_tx(ict->ict_login_sm.icl_login_resp); + } + +} + +static void +login_sm_process_request(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + uint8_t error_class = 0; + uint8_t error_detail = 0; + + /* + * First walk all the PDU's that make up this login request + * and compile all the iSCSI key-value pairs into nvlist format. + */ + + ASSERT(lsm->icl_request_nvlist == NULL); + /* create an nvlist for request key/value pairs */ + if (idm_pdu_list_to_nvlist(&lsm->icl_pdu_list, + &lsm->icl_request_nvlist, &error_detail) != IDM_STATUS_SUCCESS) { + error_class = ISCSI_STATUS_CLASS_TARGET_ERR; + SET_LOGIN_ERROR(ict, error_class, error_detail); + goto request_fail; + } + + /* Allocate a new nvlist for response key/value pairs */ + ASSERT(lsm->icl_response_nvlist == NULL); + if (nvlist_alloc(&lsm->icl_response_nvlist, NV_UNIQUE_NAME, + KM_NOSLEEP) != 0) { + error_class = ISCSI_STATUS_CLASS_TARGET_ERR; + error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + SET_LOGIN_ERROR(ict, error_class, error_detail); + goto request_fail; + } + + /* + * This would be a very good time to make sure we have + * negotiated the required values for the login phase. For + * example we definitely should have defined InitiatorName, + * and Target name regardless of our current login phase. + */ + if (!ict->ict_op.op_initial_params_set) { + if (login_sm_validate_initial_parameters(ict) != + IDM_STATUS_SUCCESS) { + goto request_fail; + } + + /* + * Now setup our session association. This includes + * create a new session or looking up an existing session, + * and if this is not a discovery session then we will + * also register this session with STMF. + */ + if (login_sm_session_bind(ict) != IDM_STATUS_SUCCESS) { + goto request_fail; + } + + if (login_sm_set_auth(ict) != IDM_STATUS_SUCCESS) { + goto request_fail; + } + + /* + * Prepend TargetAlias and PortalGroupTag + */ + if (ict->ict_op.op_discovery_session == B_FALSE) { + if ((lsm->icl_auth.ca_tgt_alias[0]) != '\0') { + (void) iscsit_reply_string(ict, + "TargetAlias", + &lsm->icl_auth.ca_tgt_alias[0]); + } + (void) iscsit_reply_numerical(ict, + "TargetPortalGroupTag", + (uint64_t)lsm->icl_tpgt_tag); + } + + ict->ict_op.op_initial_params_set = B_TRUE; + } + + if (login_sm_process_nvlist(ict) != IDM_STATUS_SUCCESS) { + goto request_fail; + } + + if (login_sm_check_security(ict) != IDM_STATUS_SUCCESS) { + goto request_fail; + } + +request_fail: + login_sm_build_login_response(ict); + iscsit_login_sm_event(ict, ILE_LOGIN_RESP_READY, NULL); + + /* clean up request_nvlist and response_nvlist */ + if (lsm->icl_request_nvlist != NULL) { + nvlist_free(lsm->icl_request_nvlist); + lsm->icl_request_nvlist = NULL; + } + if (lsm->icl_response_nvlist != NULL) { + nvlist_free(lsm->icl_response_nvlist); + lsm->icl_response_nvlist = NULL; + } +} + + +static void +login_sm_ffp_actions(iscsit_conn_t *ict) +{ + iscsit_process_negotiated_values(ict); +} + +static idm_status_t +login_sm_validate_initial_parameters(iscsit_conn_t *ict) +{ + int nvrc; + char *string_val; + uint8_t error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + uint8_t error_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS; + idm_status_t status = IDM_STATUS_FAIL; + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + + /* + * Make sure we received the required information from the initial + * login. Add these declaratives to the negotiated list and + * remove them from the request list as we go. If anything fails, + * the caller will clean-up the nvlists. + */ + + /* + * Initiator name + */ + if ((nvrc = nvlist_lookup_string(lsm->icl_request_nvlist, + "InitiatorName", &string_val)) != 0) { + goto initial_params_done; + } + if ((nvrc = nvlist_add_string(lsm->icl_negotiated_values, + "InitiatorName", string_val)) != 0) { + goto initial_params_done; + } + if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values, + "InitiatorName", &string_val)) != 0) { + goto initial_params_done; + } + lsm->icl_initiator_name = string_val; + if ((nvrc = nvlist_remove(lsm->icl_request_nvlist, + "InitiatorName", DATA_TYPE_STRING)) != 0) { + goto initial_params_done; + } + + /* + * Session type + */ + ict->ict_op.op_discovery_session = B_FALSE; + nvrc = nvlist_lookup_string(lsm->icl_request_nvlist, + "SessionType", &string_val); + if (nvrc != ENOENT && nvrc != 0) { + goto initial_params_done; + } + if (nvrc == 0) { + if (strcmp(string_val, "Discovery") == 0) { + ict->ict_op.op_discovery_session = B_TRUE; + } else if (strcmp(string_val, "Normal") != 0) { + goto initial_params_done; + } + if ((nvrc = nvlist_add_string(lsm->icl_negotiated_values, + "SessionType", string_val)) != 0) { + goto initial_params_done; + } + if ((nvrc = nvlist_remove(lsm->icl_request_nvlist, + "SessionType", DATA_TYPE_STRING)) != 0) { + goto initial_params_done; + } + } + + /* + * Must have either TargetName or SessionType==Discovery + */ + lsm->icl_target_name = NULL; + nvrc = nvlist_lookup_string(lsm->icl_request_nvlist, + "TargetName", &string_val); + if (nvrc != ENOENT && nvrc != 0) { + goto initial_params_done; + } + if (nvrc == 0) { + if ((nvrc = nvlist_add_string(lsm->icl_negotiated_values, + "TargetName", string_val)) != 0) { + goto initial_params_done; + } + if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values, + "TargetName", &string_val)) != 0) { + goto initial_params_done; + } + lsm->icl_target_name = string_val; + if ((nvrc = nvlist_remove(lsm->icl_request_nvlist, + "TargetName", DATA_TYPE_STRING)) != 0) { + goto initial_params_done; + } + } else if (ict->ict_op.op_discovery_session == B_FALSE) { + /* + * Missing target name + */ + goto initial_params_done; + } + + IDM_SM_LOG(CE_NOTE, "conn %p: initiator=%s", (void *)ict->ict_ic, + (lsm->icl_initiator_name == NULL) ? "N/A" : + lsm->icl_initiator_name); + IDM_SM_LOG(CE_NOTE, "conn %p: target=%s", (void *)ict->ict_ic, + (lsm->icl_target_name == NULL) ? "N/A" : + lsm->icl_target_name); + IDM_SM_LOG(CE_NOTE, "conn %p: sessiontype=%s", (void *)ict->ict_ic, + ict->ict_op.op_discovery_session ? "Discovery" : "Normal"); + + /* Sucess */ + status = IDM_STATUS_SUCCESS; + error_class = ISCSI_STATUS_CLASS_SUCCESS; + error_detail = ISCSI_LOGIN_STATUS_ACCEPT; + +initial_params_done: + SET_LOGIN_ERROR(ict, error_class, error_detail); + return (status); +} + + +/* + * login_sm_session_bind + * + * This function looks at the data from the initial login request + * of a new connection and either looks up and existing session, + * creates a new session, or returns an error. RFC3720 section 5.3.1 + * defines these rules: + * + * +------------------------------------------------------------------+ + * |ISID | TSIH | CID | Target action | + * +------------------------------------------------------------------+ + * |new | non-zero | any | fail the login | + * | | | | ("session does not exist") | + * +------------------------------------------------------------------+ + * |new | zero | any | instantiate a new session | + * +------------------------------------------------------------------+ + * |existing | zero | any | do session reinstatement | + * | | | | (see section 5.3.5) | + * +------------------------------------------------------------------+ + * |existing | non-zero | new | add a new connection to | + * | | existing | | the session | + * +------------------------------------------------------------------+ + * |existing | non-zero |existing| do connection reinstatement| + * | | existing | | (see section 5.3.4) | + * +------------------------------------------------------------------+ + * |existing | non-zero | any | fail the login | + * | | new | | ("session does not exist") | + * +------------------------------------------------------------------+ + * + */ + +/* + * Map an address to an address if possible. + * Returns: + * 1 - success + * 0 - address not mapable + */ + +static int +iscsit_is_v4_mapped(struct sockaddr_storage *sa, struct sockaddr_storage *v4sa) +{ + struct sockaddr_in *sin; + struct in_addr *in; + struct sockaddr_in6 *sin6; + struct in6_addr *in6; + int ret = 0; + + sin6 = (struct sockaddr_in6 *)sa; + in6 = &sin6->sin6_addr; + if ((sa->ss_family == AF_INET6) && + (IN6_IS_ADDR_V4MAPPED(in6) || IN6_IS_ADDR_V4COMPAT(in6))) { + sin = (struct sockaddr_in *)v4sa; + in = &sin->sin_addr; + v4sa->ss_family = AF_INET; + sin->sin_port = sin6->sin6_port; + IN6_V4MAPPED_TO_INADDR(in6, in); + ret = 1; + } + return (ret); +} + +static idm_status_t +login_sm_session_bind(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_tgt_t *tgt = NULL; + iscsit_tpgt_t *tpgt = NULL; + iscsit_portal_t *portal = NULL; + iscsit_sess_t *existing_sess = NULL; + iscsit_sess_t *new_sess = NULL; + iscsit_conn_t *existing_ict = NULL; + uint8_t error_class; + uint8_t error_detail; + + /* + * Look up target and then check if there are sessions or connections + * that match this request (see below). Any holds taken on objects + * must be released at the end of the function (let's keep things + * simple). + * + * If target name is set then we should have a corresponding target + * context configured. + */ + if (lsm->icl_target_name != NULL) { + /* + * iscsit_tgt_lookup implicitly takes a ref on the target + */ + ISCSIT_GLOBAL_LOCK(RW_READER); + tgt = iscsit_tgt_lookup_locked(lsm->icl_target_name); + if (tgt == NULL) { + ISCSIT_GLOBAL_UNLOCK(); + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_TGT_NOT_FOUND); + goto session_bind_error; + } else { + mutex_enter(&tgt->target_mutex); + tpgt = avl_first(&tgt->target_tpgt_list); + + if (IS_DEFAULT_TPGT(tpgt)) { + lsm->icl_tpgt_tag = ISCSIT_DEFAULT_TPGT; + } else { + /* + * Find the portal group tag for the + * login response. + */ + struct sockaddr_storage v4sa, *sa; + + sa = &ict->ict_ic->ic_laddr; + portal = iscsit_tgt_lookup_portal(tgt, + sa, &tpgt); + if (portal == NULL && + iscsit_is_v4_mapped(sa, &v4sa)) { + /* + * Try again if the local address + * was v6 mappable to v4. + */ + portal = iscsit_tgt_lookup_portal(tgt, + &v4sa, &tpgt); + + } + if (portal == NULL) { + /* + * Initiator came in on wrong address + */ + SET_LOGIN_ERROR(ict, + ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_TGT_NOT_FOUND); + mutex_exit(&tgt->target_mutex); + ISCSIT_GLOBAL_UNLOCK(); + goto session_bind_error; + } + + /* + * Need to release holds on the portal and + * tpgt after processing is complete. + */ + lsm->icl_tpgt_tag = tpgt->tpgt_tag; + iscsit_portal_rele(portal); + iscsit_tpgt_rele(tpgt); + } + + if ((tgt->target_state != TS_STMF_ONLINE) || + ((iscsit_global.global_svc_state != ISE_ENABLED) && + ((iscsit_global.global_svc_state != ISE_BUSY)))) { + SET_LOGIN_ERROR(ict, + ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_TGT_REMOVED); + mutex_exit(&tgt->target_mutex); + ISCSIT_GLOBAL_UNLOCK(); + goto session_bind_error; + } + mutex_exit(&tgt->target_mutex); + ISCSIT_GLOBAL_UNLOCK(); + } + } + + ASSERT((tgt != NULL) || (ict->ict_op.op_discovery_session == B_TRUE)); + + /* + * Check if there is an existing session matching this ISID. If + * tgt == NULL then we'll look for the session on the global list + * of discovery session. If we find a session then the ISID + * exists. + */ + existing_sess = iscsit_tgt_lookup_sess(tgt, lsm->icl_initiator_name, + lsm->icl_isid, lsm->icl_tsih, lsm->icl_tpgt_tag); + if (existing_sess != NULL) { + existing_ict = iscsit_sess_lookup_conn(existing_sess, + ict->ict_cid); + } + + /* + * If this is a discovery session, make sure it has appropriate + * parameters. + */ + if ((ict->ict_op.op_discovery_session == B_TRUE) && + ((lsm->icl_tsih != ISCSI_UNSPEC_TSIH) || (existing_sess != NULL))) { + /* XXX Do we need to check for existing ISID (sess != NULL)? */ + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_INVALID_REQUEST); + goto session_bind_error; + } + + /* + * Check the two error conditions from the table. + * + * ISID=new, TSIH=non-zero + */ + if ((existing_sess == NULL) && (lsm->icl_tsih != ISCSI_UNSPEC_TSIH)) { + /* fail the login */ + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_NO_SESSION); + goto session_bind_error; + } + + /* ISID=existing, TSIH=non-zero new */ + if ((existing_sess != NULL) && (lsm->icl_tsih != 0) && + (existing_sess->ist_tsih != lsm->icl_tsih)) { + /* fail the login */ + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_NO_SESSION); + goto session_bind_error; + } + + /* + * Handle the remaining table cases in order + */ + if (existing_sess == NULL) { + /* Should have caught this above */ + ASSERT(lsm->icl_tsih == ISCSI_UNSPEC_TSIH); + /* + * ISID=new, TSIH=zero --> instantiate a new session + */ + new_sess = iscsit_sess_create(tgt, ict, lsm->icl_cmdsn, + lsm->icl_isid, lsm->icl_tpgt_tag, lsm->icl_initiator_name, + lsm->icl_target_name, &error_class, &error_detail); + ASSERT(new_sess != NULL); + + /* Session create may have failed even if it returned a value */ + if (error_class != ISCSI_STATUS_CLASS_SUCCESS) { + SET_LOGIN_ERROR(ict, error_class, error_detail); + goto session_bind_error; + } + + /* + * If we don't already have an STMF session and this is not + * a discovery session then we need to allocate and register + * one. + */ + if (!ict->ict_op.op_discovery_session) { + if (login_sm_session_register(ict) != + IDM_STATUS_SUCCESS) { + /* login_sm_session_register sets error codes */ + goto session_bind_error; + } + } + + } else { + if (lsm->icl_tsih == ISCSI_UNSPEC_TSIH) { + /* + * ISID=existing, TSIH=zero --> Session reinstatement + */ + new_sess = iscsit_sess_reinstate(tgt, existing_sess, + ict, &error_class, &error_detail); + ASSERT(new_sess != NULL); + + if (error_class != ISCSI_STATUS_CLASS_SUCCESS) { + SET_LOGIN_ERROR(ict, error_class, error_detail); + goto session_bind_error; + } + + /* + * If we don't already have an STMF session and this is + * not a discovery session then we need to allocate and + * register one. + */ + if (!ict->ict_op.op_discovery_session) { + if (login_sm_session_register(ict) != + IDM_STATUS_SUCCESS) { + /* + * login_sm_session_register sets + * error codes + */ + goto session_bind_error; + } + } + } else { + /* + * The following code covers these two cases: + * ISID=existing, TSIH=non-zero existing, CID=new + * --> add new connection to MC/S session + * ISID=existing, TSIH=non-zero existing, CID=existing + * --> do connection reinstatement + * + * Session continuation uses this path as well + */ + cmn_err(CE_NOTE, "login_sm_session_bind: add new " + "conn/sess continue"); + if (existing_ict != NULL) { + /* + * ISID=existing, TSIH=non-zero existing, + * CID=existing --> do connection reinstatement + */ + if (iscsit_conn_reinstate(existing_ict, ict) != + IDM_STATUS_SUCCESS) { + /* + * Most likely this means the connection + * the initiator is trying to reinstate + * is not in an acceptable state. + */ + SET_LOGIN_ERROR(ict, + ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_INIT_ERR); + goto session_bind_error; + } + } + + iscsit_sess_sm_event(existing_sess, SE_CONN_IN_LOGIN, + ict); + } + } + + if (tgt != NULL) + iscsit_tgt_rele(tgt); + if (existing_sess != NULL) + iscsit_sess_rele(existing_sess); + if (existing_ict != NULL) + iscsit_conn_rele(existing_ict); + + return (IDM_STATUS_SUCCESS); + +session_bind_error: + if (tgt != NULL) + iscsit_tgt_rele(tgt); + if (existing_sess != NULL) + iscsit_sess_rele(existing_sess); + if (existing_ict != NULL) + iscsit_conn_rele(existing_ict); + + /* + * If session bind fails we will fail the login but don't destroy + * the session until later. + */ + return (IDM_STATUS_FAIL); +} + + +static idm_status_t +login_sm_set_auth(iscsit_conn_t *ict) +{ + idm_status_t idmrc = IDM_STATUS_SUCCESS; + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_ini_t *ini; + iscsit_tgt_t *tgt; + char *auth = ""; + char *radiusserver = ""; + char *radiussecret = ""; + char *chapuser = ""; + char *chapsecret = ""; + char *targetchapuser = ""; + char *targetchapsecret = ""; + char *targetalias = ""; + int i; + + ISCSIT_GLOBAL_LOCK(RW_READER); + + /* + * Set authentication method to none for discovery session. + */ + if (ict->ict_op.op_discovery_session == B_TRUE) { + lsm->icl_auth.ca_method_valid_list[0] = AM_NONE; + ISCSIT_GLOBAL_UNLOCK(); + return (idmrc); + } + + /* + * Get all the authentication parameters we need -- since we hold + * the global config lock we guarantee that the parameters will + * be consistent with each other. + */ + (void) nvlist_lookup_string(iscsit_global.global_props, + PROP_AUTH, &auth); + (void) nvlist_lookup_string(iscsit_global.global_props, + PROP_RADIUS_SERVER, &radiusserver); + (void) nvlist_lookup_string(iscsit_global.global_props, + PROP_RADIUS_SECRET, &radiussecret); + + ini = iscsit_ini_lookup_locked(lsm->icl_initiator_name); + if (ini != NULL) { + /* Get Initiator CHAP parameters */ + (void) nvlist_lookup_string(ini->ini_props, PROP_CHAP_USER, + &chapuser); + (void) nvlist_lookup_string(ini->ini_props, PROP_CHAP_SECRET, + &chapsecret); + } + + tgt = ict->ict_sess->ist_tgt; + if (tgt != NULL) { + /* See if we have a target-specific authentication setting */ + (void) nvlist_lookup_string(tgt->target_props, PROP_AUTH, + &auth); + /* Get target CHAP parameters */ + (void) nvlist_lookup_string(tgt->target_props, + PROP_TARGET_CHAP_USER, &targetchapuser); + (void) nvlist_lookup_string(tgt->target_props, + PROP_TARGET_CHAP_SECRET, &targetchapsecret); + /* Get alias */ + (void) nvlist_lookup_string(tgt->target_props, + PROP_ALIAS, &targetalias); + } + + /* Set authentication method */ + i = 0; + if (strcmp(auth, PA_AUTH_RADIUS) == 0) { + /* CHAP authentication using RADIUS server */ + lsm->icl_auth.ca_method_valid_list[i++] = AM_CHAP; + lsm->icl_auth.ca_use_radius = B_TRUE; + } else if (strcmp(auth, PA_AUTH_CHAP) == 0) { + /* Local CHAP authentication */ + lsm->icl_auth.ca_method_valid_list[i++] = AM_CHAP; + lsm->icl_auth.ca_use_radius = B_FALSE; + } else if ((strcmp(auth, PA_AUTH_NONE) == 0) || + (strcmp(auth, "") == 0)) { + /* No authentication */ + lsm->icl_auth.ca_method_valid_list[i++] = AM_NONE; + } + + /* + * If initiator/target CHAP username is not set then use the + * node name. If lsm->icl_target_name == NULL then this is + * a discovery session so we don't need to work about the target. + */ + if (strcmp(chapuser, "") == 0) { + (void) strlcpy(lsm->icl_auth.ca_ini_chapuser, + lsm->icl_initiator_name, + min(iscsiAuthStringMaxLength, MAX_ISCSI_NODENAMELEN)); + } else { + (void) strlcpy(lsm->icl_auth.ca_ini_chapuser, chapuser, + iscsiAuthStringMaxLength); + } + if ((lsm->icl_target_name != NULL) && + (strcmp(targetchapuser, "") == 0)) { + (void) strlcpy(lsm->icl_auth.ca_tgt_chapuser, + lsm->icl_target_name, + min(iscsiAuthStringMaxLength, MAX_ISCSI_NODENAMELEN)); + } else { + (void) strlcpy(lsm->icl_auth.ca_tgt_chapuser, + targetchapuser, iscsiAuthStringMaxLength); + } + + /* + * Secrets are stored in base64-encoded format so we need to + * decode them into binary form + */ + if (strcmp(chapsecret, "") == 0) { + lsm->icl_auth.ca_ini_chapsecretlen = 0; + } else { + if (iscsi_base64_str_to_binary(chapsecret, + strnlen(chapsecret, iscsiAuthStringMaxLength), + lsm->icl_auth.ca_ini_chapsecret, iscsiAuthStringMaxLength, + &lsm->icl_auth.ca_ini_chapsecretlen) != 0) { + cmn_err(CE_WARN, "Corrupted CHAP secret" + " for initiator %s", lsm->icl_initiator_name); + lsm->icl_auth.ca_ini_chapsecretlen = 0; + } + } + if (strcmp(targetchapsecret, "") == 0) { + lsm->icl_auth.ca_tgt_chapsecretlen = 0; + } else { + if (iscsi_base64_str_to_binary(targetchapsecret, + strnlen(targetchapsecret, iscsiAuthStringMaxLength), + lsm->icl_auth.ca_tgt_chapsecret, iscsiAuthStringMaxLength, + &lsm->icl_auth.ca_tgt_chapsecretlen) != 0) { + cmn_err(CE_WARN, "Corrupted CHAP secret" + " for target %s", lsm->icl_target_name); + lsm->icl_auth.ca_tgt_chapsecretlen = 0; + } + } + if (strcmp(radiussecret, "") == 0) { + lsm->icl_auth.ca_radius_secretlen = 0; + } else { + if (iscsi_base64_str_to_binary(radiussecret, + strnlen(radiussecret, iscsiAuthStringMaxLength), + lsm->icl_auth.ca_radius_secret, iscsiAuthStringMaxLength, + &lsm->icl_auth.ca_radius_secretlen) != 0) { + cmn_err(CE_WARN, "Corrupted RADIUS secret"); + lsm->icl_auth.ca_radius_secretlen = 0; + } + } + + /* + * Set alias + */ + (void) strlcpy(lsm->icl_auth.ca_tgt_alias, targetalias, + MAX_ISCSI_NODENAMELEN); + + /* + * Now that authentication parameters are setup, validate the parameters + * against the authentication mode + * Decode RADIUS server value int lsm->icl_auth.ca_radius_server + */ + if ((strcmp(auth, PA_AUTH_RADIUS) == 0) && + ((lsm->icl_auth.ca_radius_secretlen == 0) || + (strcmp(radiusserver, "") == 0) || + it_common_convert_sa(radiusserver, + &lsm->icl_auth.ca_radius_server, + DEFAULT_RADIUS_PORT) == NULL)) { + cmn_err(CE_WARN, "RADIUS authentication selected " + "for target %s but RADIUS parameters are not " + "configured.", lsm->icl_target_name); + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_TARGET_ERR, + ISCSI_LOGIN_STATUS_TARGET_ERROR); + idmrc = IDM_STATUS_FAIL; + } else if ((strcmp(auth, PA_AUTH_CHAP) == 0) && + (lsm->icl_auth.ca_ini_chapsecretlen == 0)) { + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_AUTH_FAILED); + idmrc = IDM_STATUS_FAIL; + } + + ISCSIT_GLOBAL_UNLOCK(); + + return (idmrc); +} + + +static idm_status_t +login_sm_session_register(iscsit_conn_t *ict) +{ + iscsit_sess_t *ist = ict->ict_sess; + stmf_scsi_session_t *ss; + + /* + * Hold target mutex until we have finished registering with STMF + */ + mutex_enter(&ist->ist_tgt->target_mutex); + if (ist->ist_tgt->target_state != TS_STMF_ONLINE) { + mutex_exit(&ist->ist_tgt->target_mutex); + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR, + ISCSI_LOGIN_STATUS_TGT_REMOVED); + return (IDM_STATUS_FAIL); + } + + ss = stmf_alloc(STMF_STRUCT_SCSI_SESSION, 0, + 0); + if (ss == NULL) { + mutex_exit(&ist->ist_tgt->target_mutex); + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + return (IDM_STATUS_FAIL); + } + + ss->ss_rport_id = kmem_zalloc(sizeof (scsi_devid_desc_t) + + strlen(ist->ist_initiator_name) + 1, KM_SLEEP); + (void) strcpy((char *)ss->ss_rport_id->ident, ist->ist_initiator_name); + ss->ss_rport_id->ident_length = strlen(ist->ist_initiator_name); + ss->ss_rport_id->protocol_id = PROTOCOL_iSCSI; + ss->ss_rport_id->piv = 1; + ss->ss_rport_id->code_set = CODE_SET_ASCII; + ss->ss_rport_id->association = ID_IS_TARGET_PORT; + + ss->ss_lport = ist->ist_lport; + + if (stmf_register_scsi_session(ict->ict_sess->ist_lport, ss) != + STMF_SUCCESS) { + mutex_exit(&ist->ist_tgt->target_mutex); + kmem_free(ss->ss_rport_id, + sizeof (scsi_devid_desc_t) + + strlen(ist->ist_initiator_name) + 1); + stmf_free(ss); + SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_TARGET_ERR, + ISCSI_LOGIN_STATUS_TARGET_ERROR); + return (IDM_STATUS_FAIL); + } + + ss->ss_port_private = ict->ict_sess; + ict->ict_sess->ist_stmf_sess = ss; + mutex_exit(&ist->ist_tgt->target_mutex); + + return (IDM_STATUS_SUCCESS); +} + + +static idm_status_t +login_sm_req_pdu_check(iscsit_conn_t *ict, idm_pdu_t *pdu) +{ + uint8_t csg_req; + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsi_login_hdr_t *lh = (iscsi_login_hdr_t *)pdu->isp_hdr; + iscsi_login_rsp_hdr_t *lh_resp = lsm->icl_login_resp_tmpl; + + /* + * Check CSG + */ + csg_req = ISCSI_LOGIN_CURRENT_STAGE(lh->flags); + switch (csg_req) { + case ISCSI_SECURITY_NEGOTIATION_STAGE: + case ISCSI_OP_PARMS_NEGOTIATION_STAGE: + if ((csg_req != lsm->icl_login_csg) && + (lsm->icl_login_state != ILS_LOGIN_INIT)) { + /* + * Inappropriate CSG change. Initiator can only + * change CSG after we've responded with the + * transit bit set. If we had responded with + * a CSG change previous we would have updated + * our copy of CSG. + * + * The exception is when we are in ILS_LOGIN_INIT + * state since we haven't determined our initial + * CSG value yet. + */ + goto pdu_check_fail; + } + break; + case ISCSI_FULL_FEATURE_PHASE: + default: + goto pdu_check_fail; + } + + /* + * If this is the first login PDU for a new connection then + * the session will be NULL. + */ + if (ict->ict_sess != NULL) { + /* + * We've already created a session on a previous PDU. Make + * sure this PDU is consistent with what we've already seen + */ + if ((ict->ict_cid != ntohs(lh->cid)) || + (bcmp(ict->ict_sess->ist_isid, lh->isid, + ISCSI_ISID_LEN) != 0)) { + goto pdu_check_fail; + } + } + + /* + * Make sure we are compatible with the version range + */ +#if (ISCSIT_MAX_VERSION > 0) + if ((lh->min_version > ISCSIT_MAX_VERSION) || + (lh->max_version < ISCSIT_MIN_VERSION)) { + goto pdu_check_fail; + } +#endif + + /* + * Just in case the initiator changes things up on us along the way + * check against our active_version -- we can't change the active + * version and the initiator is not *supposed* to change its + * min_version and max_version values so this should never happen. + * Of course we only do this if the response header template has + * been built. + */ + if ((lh_resp->opcode == ISCSI_OP_LOGIN_RSP) && /* header valid */ + ((lh->min_version > lh_resp->active_version) || + (lh->max_version < lh_resp->active_version))) { + goto pdu_check_fail; + } + + return (IDM_STATUS_SUCCESS); + +pdu_check_fail: + return (IDM_STATUS_FAIL); +} + +static idm_status_t +login_sm_process_nvlist(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + char *nvp_name; + nvpair_t *nvp; + nvpair_t *next_nvp; + nvpair_t *negotiated_nvp; + kv_status_t kvrc; + uint8_t error_class; + uint8_t error_detail; + idm_status_t idm_status; + + error_class = ISCSI_STATUS_CLASS_SUCCESS; + error_detail = ISCSI_LOGIN_STATUS_ACCEPT; + + /* First, request that the transport process the list */ + kvrc = idm_negotiate_key_values(ict->ict_ic, lsm->icl_request_nvlist, + lsm->icl_response_nvlist, lsm->icl_negotiated_values); + idm_kvstat_to_error(kvrc, &error_class, &error_detail); + if (error_class != ISCSI_STATUS_CLASS_SUCCESS) { + SET_LOGIN_ERROR(ict, error_class, error_detail); + idm_status = IDM_STATUS_FAIL; + return (idm_status); + } + + /* Ensure we clear transit bit if the transport layer has countered */ + if (kvrc == KV_HANDLED_NO_TRANSIT) { + lsm->icl_login_transit = B_FALSE; + } + + /* Now, move on and process the rest of the pairs */ + nvp = nvlist_next_nvpair(lsm->icl_request_nvlist, NULL); + while (nvp != NULL) { + next_nvp = nvlist_next_nvpair(lsm->icl_request_nvlist, nvp); + nvp_name = nvpair_name(nvp); + /* + * If we've already agreed upon a value then make sure this + * is not attempting to change that value. From RFC3270 + * section 5.3: + * + * "Neither the initiator nor the target should attempt to + * declare or negotiate a parameter more than once during + * login except for responses to specific keys that + * explicitly allow repeated key declarations (e.g., + * TargetAddress). An attempt to renegotiate/redeclare + * parameters not specifically allowed MUST be detected + * by the initiator and target. If such an attempt is + * detected by the target, the target MUST respond + * with Login reject (initiator error); ..." + */ + if (nvlist_lookup_nvpair(lsm->icl_negotiated_values, + nvp_name, &negotiated_nvp) == 0) { + kvrc = KV_HANDLED; + } else { + kvrc = iscsit_handle_key(ict, nvp, nvp_name); + } + + idm_kvstat_to_error(kvrc, &error_class, &error_detail); + if (error_class != ISCSI_STATUS_CLASS_SUCCESS) { + break; + } + + nvp = next_nvp; + } + + if (error_class == ISCSI_STATUS_CLASS_SUCCESS) { + idm_status = IDM_STATUS_SUCCESS; + } else { + /* supply login class/detail for login errors */ + SET_LOGIN_ERROR(ict, error_class, error_detail); + idm_status = IDM_STATUS_FAIL; + } + + return (idm_status); +} + +static idm_status_t +login_sm_check_security(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + conn_auth_t *auth = &lsm->icl_auth; + iscsit_auth_method_t *am_list = &auth->ca_method_valid_list[0]; + kv_status_t kvrc; + uint8_t error_class; + uint8_t error_detail; + idm_status_t idm_status; + + error_class = ISCSI_STATUS_CLASS_SUCCESS; + error_detail = ISCSI_LOGIN_STATUS_ACCEPT; + + /* Check authentication status. */ + if (lsm->icl_login_csg == ISCSI_SECURITY_NEGOTIATION_STAGE) { + /* + * We should have some authentication key/value pair(s) + * received from initiator and the authentication phase + * has been shifted when the key/value pair(s) are being + * handled in the previous call iscsit_handle_security_key. + * Now it turns to target to check the authentication phase + * and shift it after taking some authentication action. + */ + kvrc = iscsit_reply_security_key(ict); + idm_kvstat_to_error(kvrc, &error_class, &error_detail); + } else if (!ict->ict_login_sm.icl_auth_pass) { + /* + * Check to see if the target allows initiators to bypass the + * security check. If the target is configured to require + * authentication, we reject the connection. + */ + if (am_list[0] == AM_NONE || am_list[0] == 0) { + ict->ict_login_sm.icl_auth_pass = 1; + } else { + error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + error_detail = ISCSI_LOGIN_STATUS_AUTH_FAILED; + } + } + + if (error_class == ISCSI_STATUS_CLASS_SUCCESS) { + idm_status = IDM_STATUS_SUCCESS; + } else { + /* supply login class/detail for login errors */ + SET_LOGIN_ERROR(ict, error_class, error_detail); + idm_status = IDM_STATUS_FAIL; + } + + return (idm_status); +} + +static void +login_sm_build_login_response(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsi_login_rsp_hdr_t *lh; + int transit, text_transit = 1; + + /* + * 1. Convert response nvlist to an idm text buffer that holds + * response key-value pairs. + * 2. Build a PDU to transmit the first login response PDU + * 3. If there is more data, wait for an ack then goto step 2. + */ + ASSERT(lsm->icl_login_resp != NULL); + + if (lsm->icl_response_nvlist) { + if (lsm->icl_login_resp_itb == NULL) { + /* initialze the idm text buf to send pdus */ + lsm->icl_login_resp_itb = idm_nvlist_to_itextbuf( + lsm->icl_response_nvlist); + if (lsm->icl_login_resp_itb == NULL) { + SET_LOGIN_ERROR(ict, + ISCSI_STATUS_CLASS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + /* Still need to send the resp so continue */ + } else { + lsm->icl_login_resp_buf = + idm_pdu_init_text_data(lsm->icl_login_resp, + lsm->icl_login_resp_itb, + ISCSI_DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH, + lsm->icl_login_resp_buf, &text_transit); + } + } else { + lsm->icl_login_resp_buf = idm_pdu_init_text_data( + lsm->icl_login_resp, lsm->icl_login_resp_itb, + ISCSI_DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH, + lsm->icl_login_resp_buf, &text_transit); + } + } else { + lsm->icl_login_resp->isp_data = NULL; + lsm->icl_login_resp->isp_datalen = 0; + } + + /* + * Use the BHS header values from the response template + */ + bcopy(lsm->icl_login_resp_tmpl, + lsm->icl_login_resp->isp_hdr, sizeof (iscsi_login_rsp_hdr_t)); + + lh = (iscsi_login_rsp_hdr_t *)lsm->icl_login_resp->isp_hdr; + + /* Set error class/detail */ + lh->status_class = lsm->icl_login_resp_err_class; + lh->status_detail = lsm->icl_login_resp_err_detail; + /* Set CSG, NSG and Transit */ + lh->flags = 0; + lh->flags |= lsm->icl_login_csg << 2; + + + if (lh->status_class == ISCSI_STATUS_CLASS_SUCCESS) { + if (lsm->icl_login_transit && + lsm->icl_auth_pass != 0) { + transit = 1; + } else { + transit = 0; + } + /* + * inititalize the text data + */ + if (transit == 1 && text_transit == 1) { + lh->flags |= lsm->icl_login_nsg; + lsm->icl_login_csg = lsm->icl_login_nsg; + lh->flags |= ISCSI_FLAG_LOGIN_TRANSIT; + } else { + lh->flags &= ~ISCSI_FLAG_LOGIN_TRANSIT; + } + + /* If we are transitioning to FFP then set TSIH */ + if (transit && (lh->flags & ISCSI_FLAG_LOGIN_TRANSIT) && + lsm->icl_login_csg == ISCSI_FULL_FEATURE_PHASE) { + lh->tsid = htons(ict->ict_sess->ist_tsih); + } + } else { + lsm->icl_login_resp->isp_data = 0; + lsm->icl_login_resp->isp_datalen = 0; + } +} + +static kv_status_t +iscsit_handle_key(iscsit_conn_t *ict, nvpair_t *nvp, char *nvp_name) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc; + const idm_kv_xlate_t *ikvx; + + ikvx = idm_lookup_kv_xlate(nvp_name, strlen(nvp_name)); + if (ikvx->ik_key_id == KI_MAX_KEY) { + /* + * Any key not understood by the acceptor may be igonred + * by the acceptor without affecting the basic function. + * However, the answer for a key not understood MUST be + * key=NotUnderstood. + */ + kvrc = iscsit_reply_string(ict, nvp_name, + ISCSI_TEXT_NOTUNDERSTOOD); + } else { + kvrc = iscsit_handle_common_key(ict, nvp, ikvx); + if (kvrc == KV_UNHANDLED) { + switch (lsm->icl_login_csg) { + case ISCSI_SECURITY_NEGOTIATION_STAGE: + kvrc = iscsit_handle_security_key( + ict, nvp, ikvx); + break; + case ISCSI_OP_PARMS_NEGOTIATION_STAGE: + kvrc = iscsit_handle_operational_key( + ict, nvp, ikvx); + break; + case ISCSI_FULL_FEATURE_PHASE: + default: + /* What are we doing here? */ + ASSERT(0); + kvrc = KV_UNHANDLED; + } + } + } + + return (kvrc); +} + +static kv_status_t +iscsit_handle_common_key(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc; + char *string_val; + int nvrc; + + switch (ikvx->ik_key_id) { + case KI_INITIATOR_NAME: + case KI_INITIATOR_ALIAS: + nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp); + kvrc = idm_nvstat_to_kvstat(nvrc); + break; + case KI_TARGET_NAME: + /* We'll validate the target during login_sm_session_bind() */ + nvrc = nvpair_value_string(nvp, &string_val); + ASSERT(nvrc == 0); /* We built this nvlist */ + + nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp); + kvrc = idm_nvstat_to_kvstat(nvrc); + break; + case KI_TARGET_ALIAS: + case KI_TARGET_ADDRESS: + case KI_TARGET_PORTAL_GROUP_TAG: + kvrc = KV_TARGET_ONLY; /* Only the target can declare this */ + break; + case KI_SESSION_TYPE: + /* + * If we don't receive this key on the initial login + * we assume this is a normal session. + */ + nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp); + kvrc = idm_nvstat_to_kvstat(nvrc); + nvrc = nvpair_value_string(nvp, &string_val); + ASSERT(nvrc == 0); /* We built this nvlist */ + ict->ict_op.op_discovery_session = + strcmp(string_val, "Discovery") == 0 ? B_TRUE : B_FALSE; + break; + default: + /* + * This is not really an error but we should + * leave this nvpair on the list since we + * didn't do anything with it. Either + * the security or operational phase + * handling functions should process it. + */ + kvrc = KV_UNHANDLED; + break; + } + + return (kvrc); +} + +static kv_status_t +iscsit_handle_security_key(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + iscsit_auth_client_t *client = &lsm->icl_auth_client; + iscsikey_id_t kv_id; + kv_status_t kvrc; + iscsit_auth_handler_t handler; + + /* + * After all of security keys are handled, this function will + * be called again to verify current authentication status + * and perform some actual authentication work. At this time, + * the nvp and ikvx will be passed in as NULLs. + */ + if (ikvx != NULL) { + kv_id = ikvx->ik_key_id; + } else { + kv_id = 0; + } + + handler = iscsit_auth_get_handler(client, kv_id); + if (handler) { + kvrc = handler(ict, nvp, ikvx); + } else { + kvrc = KV_UNHANDLED; /* invalid request */ + } + + return (kvrc); +} + +static kv_status_t +iscsit_reply_security_key(iscsit_conn_t *ict) +{ + return (iscsit_handle_security_key(ict, NULL, NULL)); +} + +static kv_status_t +iscsit_handle_operational_key(iscsit_conn_t *ict, nvpair_t *nvp, + const idm_kv_xlate_t *ikvx) +{ + kv_status_t kvrc = KV_UNHANDLED; + boolean_t bool_val; + uint64_t num_val; + int nvrc; + + /* + * Retrieve values. All value lookups are expected to succeed + * since we build the nvlist while decoding the text buffer. This + * step is intended to eliminate some duplication of code (for example + * we only need to code the numerical value lookup once). We will + * handle the values (if necessary) below. + */ + switch (ikvx->ik_key_id) { + /* Lists */ + case KI_HEADER_DIGEST: + case KI_DATA_DIGEST: + break; + /* Booleans */ + case KI_INITIAL_R2T: + case KI_IMMEDIATE_DATA: + case KI_DATA_PDU_IN_ORDER: + case KI_DATA_SEQUENCE_IN_ORDER: + case KI_IFMARKER: + case KI_OFMARKER: + nvrc = nvpair_value_boolean_value(nvp, &bool_val); + ASSERT(nvrc == 0); /* We built this nvlist */ + break; + /* Numericals */ + case KI_MAX_CONNECTIONS: + case KI_MAX_RECV_DATA_SEGMENT_LENGTH: + case KI_MAX_BURST_LENGTH: + case KI_FIRST_BURST_LENGTH: + case KI_DEFAULT_TIME_2_WAIT: + case KI_DEFAULT_TIME_2_RETAIN: + case KI_MAX_OUTSTANDING_R2T: + case KI_ERROR_RECOVERY_LEVEL: + nvrc = nvpair_value_uint64(nvp, &num_val); + ASSERT(nvrc == 0); + break; + /* Ranges */ + case KI_OFMARKERINT: + case KI_IFMARKERINT: + break; + default: + break; + } + + /* + * Now handle the values according to the key name. Sometimes we + * don't care what the value is -- in that case we just add the nvpair + * to the negotiated values list. + */ + switch (ikvx->ik_key_id) { + case KI_HEADER_DIGEST: + kvrc = iscsit_handle_digest(ict, nvp, ikvx); + break; + case KI_DATA_DIGEST: + kvrc = iscsit_handle_digest(ict, nvp, ikvx); + break; + case KI_INITIAL_R2T: + /* We *require* INITIAL_R2T=yes */ + kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx, + B_TRUE); + break; + case KI_IMMEDIATE_DATA: + /* + * For now we *require* IMMEDIATE_DATA=no. + */ + kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx, + B_FALSE); + break; + case KI_DATA_PDU_IN_ORDER: + kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx, + B_TRUE); + break; + case KI_DATA_SEQUENCE_IN_ORDER: + /* We allow any value for DATA_SEQUENCE_IN_ORDER */ + kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx, + bool_val); + break; + case KI_OFMARKER: + case KI_IFMARKER: + /* We don't support markers */ + kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx, + B_FALSE); + break; + case KI_MAX_CONNECTIONS: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_CONNECTIONS, + ISCSI_MAX_CONNECTIONS, + ISCSIT_MAX_CONNECTIONS); + break; + case KI_MAX_RECV_DATA_SEGMENT_LENGTH: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_RECV_DATA_SEGMENT_LENGTH, + ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH, + ISCSIT_MAX_RECV_DATA_SEGMENT_LENGTH); + break; + case KI_MAX_BURST_LENGTH: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_MAX_BURST_LENGTH, + ISCSI_MAX_BURST_LENGTH, + ISCSIT_MAX_BURST_LENGTH); + break; + case KI_FIRST_BURST_LENGTH: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_FIRST_BURST_LENGTH, + ISCSI_MAX_FIRST_BURST_LENGTH, + ISCSIT_MAX_FIRST_BURST_LENGTH); + break; + case KI_DEFAULT_TIME_2_WAIT: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_TIME2WAIT, + ISCSI_MAX_TIME2WAIT, + ISCSIT_MAX_TIME2WAIT); + break; + case KI_DEFAULT_TIME_2_RETAIN: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_TIME2RETAIN, + ISCSI_MAX_TIME2RETAIN, + ISCSIT_MAX_TIME2RETAIN); + break; + case KI_MAX_OUTSTANDING_R2T: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_MAX_OUTSTANDING_R2T, + ISCSI_MAX_OUTSTANDING_R2T, + ISCSIT_MAX_OUTSTANDING_R2T); + break; + case KI_ERROR_RECOVERY_LEVEL: + kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx, + ISCSI_MIN_ERROR_RECOVERY_LEVEL, + ISCSI_MAX_ERROR_RECOVERY_LEVEL, + ISCSIT_MAX_ERROR_RECOVERY_LEVEL); + break; + case KI_OFMARKERINT: + case KI_IFMARKERINT: + kvrc = iscsit_reply_string(ict, ikvx->ik_key_name, + ISCSI_TEXT_IRRELEVANT); + break; + default: + kvrc = KV_UNHANDLED; /* invalid request */ + break; + } + + return (kvrc); +} + +static kv_status_t +iscsit_reply_numerical(iscsit_conn_t *ict, + const char *nvp_name, const uint64_t value) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc; + int nvrc; + + nvrc = nvlist_add_uint64(lsm->icl_response_nvlist, + nvp_name, value); + kvrc = idm_nvstat_to_kvstat(nvrc); + + return (kvrc); +} + +static kv_status_t +iscsit_reply_string(iscsit_conn_t *ict, + const char *nvp_name, const char *text) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc; + int nvrc; + + nvrc = nvlist_add_string(lsm->icl_response_nvlist, + nvp_name, text); + kvrc = idm_nvstat_to_kvstat(nvrc); + + return (kvrc); +} + +static kv_status_t +iscsit_handle_digest(iscsit_conn_t *ict, nvpair_t *choices, + const idm_kv_xlate_t *ikvx) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc = KV_VALUE_ERROR; + int nvrc; + nvpair_t *digest_choice; + char *digest_choice_string; + + /* + * Need to add persistent config here if we want users to allow + * disabling of digests on the target side. You could argue that + * this makes things too complicated... just let the initiator state + * what it wants and we'll take it. For now that's exactly what + * we'll do. + * + * Basic digest negotiation happens here at iSCSI level. IDM + * can override this during negotiate_key_values phase to + * decline to set up any digest processing. + */ + digest_choice = idm_get_next_listvalue(choices, NULL); + + /* + * Loop through all choices. As soon as we find a choice + * that we support add the value to our negotiated values list + * and respond with that value in the login response. + */ + while (digest_choice != NULL) { + nvrc = nvpair_value_string(digest_choice, + &digest_choice_string); + ASSERT(nvrc == 0); + + if ((strcasecmp(digest_choice_string, "crc32c") == 0) || + (strcasecmp(digest_choice_string, "none") == 0)) { + /* Add to negotiated values list */ + nvrc = nvlist_add_string(lsm->icl_negotiated_values, + ikvx->ik_key_name, digest_choice_string); + kvrc = idm_nvstat_to_kvstat(nvrc); + if (nvrc == 0) { + /* Add to login response list */ + nvrc = nvlist_add_string( + lsm->icl_response_nvlist, + ikvx->ik_key_name, digest_choice_string); + kvrc = idm_nvstat_to_kvstat(nvrc); + } + break; + } + digest_choice = idm_get_next_listvalue(choices, + digest_choice); + } + + if (digest_choice == NULL) + kvrc = KV_VALUE_ERROR; + + return (kvrc); +} + +static kv_status_t +iscsit_handle_boolean(iscsit_conn_t *ict, nvpair_t *nvp, boolean_t value, + const idm_kv_xlate_t *ikvx, boolean_t iscsit_value) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc; + int nvrc; + + if (value != iscsit_value) { + /* Respond back to initiator with our value */ + value = iscsit_value; + lsm->icl_login_transit = B_FALSE; + nvrc = 0; + } else { + /* Add this to our negotiated values */ + nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, + nvp); + } + + /* Response of Simple-value Negotiation */ + if (nvrc == 0 && !ikvx->ik_declarative) { + nvrc = nvlist_add_boolean_value( + lsm->icl_response_nvlist, ikvx->ik_key_name, value); + } + kvrc = idm_nvstat_to_kvstat(nvrc); + + return (kvrc); +} + +static kv_status_t +iscsit_handle_numerical(iscsit_conn_t *ict, nvpair_t *nvp, uint64_t value, + const idm_kv_xlate_t *ikvx, + uint64_t iscsi_min_value, uint64_t iscsi_max_value, + uint64_t iscsit_max_value) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + kv_status_t kvrc; + int nvrc; + + /* Validate against standard */ + if ((value < iscsi_min_value) || (value > iscsi_max_value)) { + kvrc = KV_VALUE_ERROR; + } else { + if (value > iscsit_max_value) { + /* Respond back to initiator with our value */ + value = iscsit_max_value; + lsm->icl_login_transit = B_FALSE; + nvrc = 0; + } else { + /* Add this to our negotiated values */ + nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, + nvp); + } + + /* Response of Simple-value Negotiation */ + if (nvrc == 0 && !ikvx->ik_declarative) { + nvrc = nvlist_add_uint64(lsm->icl_response_nvlist, + ikvx->ik_key_name, value); + } + kvrc = idm_nvstat_to_kvstat(nvrc); + } + + return (kvrc); +} + + +static void +iscsit_process_negotiated_values(iscsit_conn_t *ict) +{ + iscsit_conn_login_t *lsm = &ict->ict_login_sm; + char *string_val; + boolean_t boolean_val; + uint64_t uint64_val; + int nvrc; + idm_status_t idmrc; + + /* Let the IDM level activate its parameters first */ + idmrc = idm_notice_key_values(ict->ict_ic, lsm->icl_negotiated_values); + ASSERT(idmrc == IDM_STATUS_SUCCESS); + + /* + * Initiator alias and target alias + */ + if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values, + "InitiatorAlias", &string_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_sess->ist_initiator_alias = + kmem_alloc(strlen(string_val) + 1, KM_SLEEP); + (void) strcpy(ict->ict_sess->ist_initiator_alias, string_val); + } + + if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values, + "TargetAlias", &string_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_sess->ist_target_alias = + kmem_alloc(strlen(string_val) + 1, KM_SLEEP); + (void) strcpy(ict->ict_sess->ist_target_alias, string_val); + } + + /* + * Operational parameters. We process SessionType when it is + * initially received since it is required on the initial login. + */ + if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values, + "InitialR2T", &boolean_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_initial_r2t = boolean_val; + } + + if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values, + "ImmediateData", &boolean_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_immed_data = boolean_val; + } + + if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values, + "DataPDUInOrder", &boolean_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_data_pdu_in_order = boolean_val; + } + + if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values, + "DataSequenceInOrder", &boolean_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_data_sequence_in_order = boolean_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "MaxConnections", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_max_connections = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "MaxRecvDataSegmentLength", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_max_recv_data_segment_length = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "MaxBurstLength", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_max_burst_length = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "FirstBurstLength", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_first_burst_length = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "DefaultTime2Wait", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_default_time_2_wait = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "DefaultTime2Retain", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_default_time_2_retain = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "MaxOutstandingR2T", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_max_outstanding_r2t = uint64_val; + } + + if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values, + "ErrorRecoveryLevel", &uint64_val)) != ENOENT) { + ASSERT(nvrc == 0); + ict->ict_op.op_error_recovery_level = uint64_val; + } +} diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiusauth.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiusauth.c new file mode 100644 index 000000000000..ba515c2b5916 --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiusauth.c @@ -0,0 +1,183 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* Forward declaration */ +/* + * Annotate the radius_attr_t objects with authentication data. + */ +static +void +set_radius_attrs(radius_packet_data_t *req, + char *target_chap_name, + unsigned char *target_response, + uint32_t response_length, + uint8_t *challenge, + uint32_t challenge_length); + +/* + * See radius_auth.h. + */ +/* ARGSUSED */ +chap_validation_status_type +iscsit_radius_chap_validate(char *target_chap_name, + char *initiator_chap_name, + uint8_t *challenge, + uint32_t challenge_length, + uint8_t *target_response, + uint32_t response_length, + uint8_t identifier, + iscsi_ipaddr_t rad_svr_ip_addr, + uint32_t rad_svr_port, + uint8_t *rad_svr_shared_secret, + uint32_t rad_svr_shared_secret_len) +{ + chap_validation_status_type validation_status; + char lbolt[64]; + int rcv_status; + void *socket; + radius_packet_data_t req; + radius_packet_data_t resp; + MD5_CTX context; + uint8_t md5_digest[16]; /* MD5 digest length 16 */ + uint8_t random_number[16]; + + if (rad_svr_shared_secret_len == 0) { + /* The secret must not be empty (section 3, RFC 2865) */ + cmn_err(CE_WARN, "empty RADIUS shared secret"); + return (CHAP_VALIDATION_BAD_RADIUS_SECRET); + } + + bzero(&req, sizeof (radius_packet_data_t)); + + req.identifier = identifier; + req.code = RAD_ACCESS_REQ; + set_radius_attrs(&req, + target_chap_name, + target_response, + response_length, + challenge, + challenge_length); + + /* Prepare the request authenticator */ + MD5Init(&context); + bzero(&md5_digest, 16); + /* First, the shared secret */ + MD5Update(&context, rad_svr_shared_secret, rad_svr_shared_secret_len); + /* Then a unique number - use lbolt plus a random number */ + bzero(&lbolt, sizeof (lbolt)); + (void) snprintf(lbolt, sizeof (lbolt), "%lx", ddi_get_lbolt()); + MD5Update(&context, (uint8_t *)lbolt, strlen(lbolt)); + bzero(&random_number, sizeof (random_number)); + (void) random_get_pseudo_bytes(random_number, sizeof (random_number)); + MD5Update(&context, random_number, sizeof (random_number)); + MD5Final(md5_digest, &context); + bcopy(md5_digest, &req.authenticator, RAD_AUTHENTICATOR_LEN); + + socket = idm_socreate(PF_INET, SOCK_DGRAM, 0); + if (socket == NULL) { + /* Error obtaining socket for RADIUS use */ + return (CHAP_VALIDATION_RADIUS_ACCESS_ERROR); + } + + /* Send the authentication access request to the RADIUS server */ + if (iscsit_snd_radius_request(socket, + rad_svr_ip_addr, + rad_svr_port, + &req) != 0) { + idm_soshutdown(socket); + idm_sodestroy(socket); + return (CHAP_VALIDATION_RADIUS_ACCESS_ERROR); + } + + bzero(&resp, sizeof (radius_packet_data_t)); + /* Analyze the response coming through from the same socket. */ + rcv_status = iscsit_rcv_radius_response(socket, + rad_svr_shared_secret, + rad_svr_shared_secret_len, + req.authenticator, &resp); + if (rcv_status == RAD_RSP_RCVD_SUCCESS) { + if (resp.code == RAD_ACCESS_ACPT) { + validation_status = CHAP_VALIDATION_PASSED; + } else if (resp.code == RAD_ACCESS_REJ) { + validation_status = CHAP_VALIDATION_INVALID_RESPONSE; + } else { + validation_status = + CHAP_VALIDATION_UNKNOWN_RADIUS_CODE; + } + } else if (rcv_status == RAD_RSP_RCVD_AUTH_FAILED) { + validation_status = CHAP_VALIDATION_BAD_RADIUS_SECRET; + } else { + validation_status = CHAP_VALIDATION_RADIUS_ACCESS_ERROR; + } + + /* Done! Close the socket. */ + idm_soshutdown(socket); + idm_sodestroy(socket); + + return (validation_status); +} + +/* See forward declaration. */ +static void +set_radius_attrs(radius_packet_data_t *req, + char *target_chap_name, + unsigned char *target_response, + uint32_t response_length, + uint8_t *challenge, + uint32_t challenge_length) +{ + req->attrs[0].attr_type_code = RAD_USER_NAME; + (void) strncpy((char *)req->attrs[0].attr_value, + (const char *)target_chap_name, + strlen(target_chap_name)); + req->attrs[0].attr_value_len = strlen(target_chap_name); + + req->attrs[1].attr_type_code = RAD_CHAP_PASSWORD; + bcopy(target_response, + (char *)req->attrs[1].attr_value, + min(response_length, sizeof (req->attrs[1].attr_value))); + /* A target response is an MD5 hash thus its length has to be 16. */ + req->attrs[1].attr_value_len = 16; + + req->attrs[2].attr_type_code = RAD_CHAP_CHALLENGE; + bcopy(challenge, + (char *)req->attrs[2].attr_value, + min(challenge_length, sizeof (req->attrs[2].attr_value))); + req->attrs[2].attr_value_len = challenge_length; + + /* 3 attributes associated with each RADIUS packet. */ + req->num_of_attrs = 3; +} diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiuspacket.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiuspacket.c new file mode 100644 index 000000000000..2441e3b65c37 --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiuspacket.c @@ -0,0 +1,390 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +static void encode_chap_password(int identifier, int chap_passwd_len, + uint8_t *chap_passwd, uint8_t *result); + +static size_t iscsit_net_recvmsg(void *socket, struct msghdr *msg, + int timeout); + +/* + * See radius_packet.h. + */ +int +iscsit_snd_radius_request(void *socket, iscsi_ipaddr_t rsvr_ip_addr, + uint32_t rsvr_port, radius_packet_data_t *req_data) +{ + int i; /* Loop counter. */ + int data_len; + int len; + ushort_t total_length; /* Has to be 2 octets in size */ + uint8_t *ptr; /* Pointer to RADIUS packet data */ + uint8_t *length_ptr; /* Points to the Length field of the */ + /* packet. */ + uint8_t *data; /* RADIUS data to be sent */ + radius_attr_t *req_attr; /* Request attributes */ + radius_packet_t *packet; /* Outbound RADIUS packet */ + union { + struct sockaddr_in s_in4; + struct sockaddr_in6 s_in6; + } sa_rsvr; /* Socket address of the server */ + int err; + + /* + * Create a RADIUS packet with minimal length for now. + */ + total_length = MIN_RAD_PACKET_LEN; + data = kmem_zalloc(MAX_RAD_PACKET_LEN, KM_SLEEP); + packet = (radius_packet_t *)data; + packet->code = req_data->code; + packet->identifier = req_data->identifier; + bcopy(req_data->authenticator, packet->authenticator, + RAD_AUTHENTICATOR_LEN); + ptr = packet->data; + + /* Loop over all attributes of the request. */ + for (i = 0; i < req_data->num_of_attrs; i++) { + if (total_length > MAX_RAD_PACKET_LEN) { + /* The packet has exceed its maximum size. */ + kmem_free(data, MAX_RAD_PACKET_LEN); + return (-1); + } + + req_attr = &req_data->attrs[i]; + *ptr++ = (req_attr->attr_type_code & 0xFF); + length_ptr = ptr; + /* Length is 2 octets - RFC 2865 section 3 */ + *ptr++ = 2; + total_length += 2; + + /* If the attribute is CHAP-Password, encode it. */ + if (req_attr->attr_type_code == RAD_CHAP_PASSWORD) { + /* + * Identifier plus CHAP response. RFC 2865 + * section 5.3. + */ + uint8_t encoded_chap_passwd[ + RAD_CHAP_PASSWD_STR_LEN + RAD_IDENTIFIER_LEN + 1]; + encode_chap_password( + req_data->identifier, + req_attr->attr_value_len, + req_attr->attr_value, + encoded_chap_passwd); + + req_attr->attr_value_len = + RAD_CHAP_PASSWD_STR_LEN + RAD_IDENTIFIER_LEN; + + bcopy(encoded_chap_passwd, + req_attr->attr_value, + req_attr->attr_value_len); + } + + len = req_attr->attr_value_len; + *length_ptr += len; + + bcopy(req_attr->attr_value, ptr, req_attr->attr_value_len); + ptr += req_attr->attr_value_len; + + total_length += len; + } /* Done looping over all attributes */ + + data_len = total_length; + total_length = htons(total_length); + bcopy(&total_length, packet->length, sizeof (ushort_t)); + + /* + * Send the packet to the RADIUS server. + */ + bzero((char *)&sa_rsvr, sizeof (sa_rsvr)); + if (rsvr_ip_addr.i_insize == sizeof (in_addr_t)) { + + /* IPv4 */ + sa_rsvr.s_in4.sin_family = AF_INET; + sa_rsvr.s_in4.sin_addr.s_addr = + rsvr_ip_addr.i_addr.in4.s_addr; + sa_rsvr.s_in4.sin_port = htons((ushort_t)rsvr_port); + + err = idm_sosendto(socket, data, data_len, + (struct sockaddr *)&sa_rsvr.s_in4, + sizeof (struct sockaddr_in)); + kmem_free(data, MAX_RAD_PACKET_LEN); + return (err); + } else if (rsvr_ip_addr.i_insize == sizeof (in6_addr_t)) { + /* IPv6 */ + sa_rsvr.s_in6.sin6_family = AF_INET6; + bcopy(rsvr_ip_addr.i_addr.in6.s6_addr, + sa_rsvr.s_in6.sin6_addr.s6_addr, sizeof (struct in6_addr)); + sa_rsvr.s_in6.sin6_port = htons((ushort_t)rsvr_port); + + err = idm_sosendto(socket, data, data_len, + (struct sockaddr *)&sa_rsvr.s_in6, + sizeof (struct sockaddr_in6)); + kmem_free(data, MAX_RAD_PACKET_LEN); + return (err); + } else { + /* Invalid IP address for RADIUS server. */ + kmem_free(data, MAX_RAD_PACKET_LEN); + return (-1); + } +} + +/* + * See radius_packet.h. + */ +int +iscsit_rcv_radius_response(void *socket, uint8_t *shared_secret, + uint32_t shared_secret_len, uint8_t *req_authenticator, + radius_packet_data_t *resp_data) +{ + radius_packet_t *packet; + MD5_CTX context; + uint8_t *tmp_data; + uint8_t md5_digest[16]; /* MD5 Digest Length 16 */ + uint16_t declared_len = 0; + size_t received_len = 0; + + struct iovec iov[1]; + struct nmsghdr msg; + struct sonode *so = (struct sonode *)socket; + int ret = 0; + + tmp_data = kmem_zalloc(MAX_RAD_PACKET_LEN, KM_SLEEP); + iov[0].iov_base = (char *)tmp_data; + iov[0].iov_len = MAX_RAD_PACKET_LEN; + + bzero(&msg, sizeof (msg)); + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = MSG_WAITALL; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + (void) VOP_IOCTL(SOTOV(so), I_POP, 0, FKIOCTL, CRED(), &ret, NULL); + if (ret != 0) { + return (RAD_RSP_RCVD_NO_DATA); + } + + received_len = iscsit_net_recvmsg(socket, &msg, RAD_RCV_TIMEOUT); + + if (received_len <= (size_t)0) { + kmem_free(tmp_data, MAX_RAD_PACKET_LEN); + return (RAD_RSP_RCVD_NO_DATA); + } + + /* + * Check if the received packet length is within allowable range. + * RFC 2865 section 3. + */ + if (received_len < MIN_RAD_PACKET_LEN) { + kmem_free(tmp_data, MAX_RAD_PACKET_LEN); + return (RAD_RSP_RCVD_PROTOCOL_ERR); + } else if (received_len > MAX_RAD_PACKET_LEN) { + kmem_free(tmp_data, MAX_RAD_PACKET_LEN); + return (RAD_RSP_RCVD_PROTOCOL_ERR); + } + + packet = (radius_packet_t *)tmp_data; + bcopy(packet->length, &declared_len, sizeof (ushort_t)); + declared_len = ntohs(declared_len); + + /* + * Discard packet with received length shorter than declared + * length. RFC 2865 section 3. + */ + if (received_len < declared_len) { + kmem_free(tmp_data, MAX_RAD_PACKET_LEN); + return (RAD_RSP_RCVD_PROTOCOL_ERR); + } + + /* + * Check if the declared packet length is within allowable range. + * RFC 2865 section 3. + */ + if (declared_len < MIN_RAD_PACKET_LEN) { + kmem_free(tmp_data, MAX_RAD_PACKET_LEN); + return (RAD_RSP_RCVD_PROTOCOL_ERR); + } else if (declared_len > MAX_RAD_PACKET_LEN) { + kmem_free(tmp_data, MAX_RAD_PACKET_LEN); + return (RAD_RSP_RCVD_PROTOCOL_ERR); + } + + /* + * Authenticate the incoming packet, using the following algorithm + * (RFC 2865 section 3): + * + * MD5(Code+ID+Length+RequestAuth+Attributes+Secret) + * + * Code = RADIUS packet code + * ID = RADIUS packet identifier + * Length = Declared length of the packet + * RequestAuth = The request authenticator + * Attributes = The response attributes + * Secret = The shared secret + */ + MD5Init(&context); + bzero(&md5_digest, 16); + MD5Update(&context, &packet->code, 1); + MD5Update(&context, &packet->identifier, 1); + MD5Update(&context, packet->length, 2); + MD5Update(&context, req_authenticator, RAD_AUTHENTICATOR_LEN); + + /* + * Include response attributes only if there is a payload + * If the received length is greater than the declared length, + * trust the declared length and shorten the packet (i.e., to + * treat the octets outside the range of the Length field as + * padding - RFC 2865 section 3). + */ + if (declared_len > RAD_PACKET_HDR_LEN) { + /* Response Attributes */ + MD5Update(&context, packet->data, + declared_len - RAD_PACKET_HDR_LEN); + } + MD5Update(&context, shared_secret, shared_secret_len); + MD5Final(md5_digest, &context); + + if (bcmp(md5_digest, packet->authenticator, RAD_AUTHENTICATOR_LEN) + != 0) { + kmem_free(tmp_data, MAX_RAD_PACKET_LEN); + return (RAD_RSP_RCVD_AUTH_FAILED); + } + + /* + * Annotate the RADIUS packet data with the data we received from + * the server. + */ + resp_data->code = packet->code; + resp_data->identifier = packet->identifier; + + kmem_free(tmp_data, MAX_RAD_PACKET_LEN); + return (RAD_RSP_RCVD_SUCCESS); +} + +/* + * encode_chap_password - + * + * Encode a CHAP-Password attribute. This function basically prepends + * the identifier in front of chap_passwd and copy the results to + * *result. + */ +static void +encode_chap_password(int identifier, int chap_passwd_len, + uint8_t *chap_passwd, uint8_t *result) +{ + result[0] = (uint8_t)identifier; + bcopy(chap_passwd, &result[1], chap_passwd_len); +} +/* + * iscsi_net_recvmsg - receive message on socket + */ +/* ARGSUSED */ +static size_t +iscsit_net_recvmsg(void *socket, struct msghdr *msg, int timeout) +{ + int idx; + int total_len = 0; + struct uio uio; + uchar_t pri = 0; + int prflag = MSG_ANY; + rval_t rval; + struct sonode *sonode = (struct sonode *)socket; + + /* Initialization of the uio structure. */ + bzero(&uio, sizeof (uio)); + uio.uio_iov = msg->msg_iov; + uio.uio_iovcnt = msg->msg_iovlen; + uio.uio_segflg = UIO_SYSSPACE; + + for (idx = 0; idx < msg->msg_iovlen; idx++) { + total_len += (msg->msg_iov)[idx].iov_len; + } + uio.uio_resid = total_len; + + /* If timeout requested on receive */ + if (timeout > 0) { + boolean_t loopback = B_FALSE; + /* And this isn't a loopback connection */ + if (sonode->so_laddr.soa_sa->sa_family == AF_INET) { + struct sockaddr_in *lin = (struct sockaddr_in *) + ((void *)sonode->so_laddr.soa_sa); + struct sockaddr_in *fin = (struct sockaddr_in *) + ((void *)sonode->so_faddr.soa_sa); + + if ((lin->sin_family == fin->sin_family) && + (bcmp(&lin->sin_addr, &fin->sin_addr, + sizeof (struct in_addr)) == 0)) { + loopback = B_TRUE; + } + } else { + struct sockaddr_in6 *lin6 = (struct sockaddr_in6 *) + ((void *)sonode->so_laddr.soa_sa); + struct sockaddr_in6 *fin6 = (struct sockaddr_in6 *) + ((void *)sonode->so_faddr.soa_sa); + + if ((lin6->sin6_family == fin6->sin6_family) && + (bcmp(&lin6->sin6_addr, &fin6->sin6_addr, + sizeof (struct in6_addr)) == 0)) { + loopback = B_TRUE; + } + } + + if (loopback == B_FALSE) { + /* + * Then poll device for up to the timeout + * period or the requested data is received. + */ + if (kstrgetmsg(SOTOV(sonode), + NULL, NULL, &pri, &prflag, timeout * 1000, + &rval) == ETIME) { + return (0); + } + } + } + + /* + * Receive the requested data. Block until all + * data is received. + * + * resid occurs only when the connection is + * disconnected. In that case it will return + * the amount of data that was not received. + * In general this is the total amount we + * requested. + */ + (void) sorecvmsg((struct sonode *)socket, msg, &uio); + return (total_len - uio.uio_resid); +} diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_sess.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_sess.c new file mode 100644 index 000000000000..0842c49df36e --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_sess.c @@ -0,0 +1,792 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define ISCSIT_SESS_SM_STRINGS +#include + + + +typedef struct { + list_node_t se_ctx_node; + iscsit_session_event_t se_ctx_event; + iscsit_conn_t *se_event_data; +} sess_event_ctx_t; + +static void +sess_sm_event_locked(iscsit_sess_t *ist, iscsit_session_event_t event, +iscsit_conn_t *ict); + +static void +sess_sm_event_dispatch(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q1_free(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q2_active(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q3_logged_in(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q4_failed(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q5_continue(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q6_done(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_q7_error(iscsit_sess_t *ist, sess_event_ctx_t *ctx); + +static void +sess_sm_new_state(iscsit_sess_t *ist, sess_event_ctx_t *ctx, + iscsit_session_state_t new_state); + + +static uint16_t +iscsit_tsih_alloc(void) +{ + uintptr_t result; + + result = (uintptr_t)vmem_alloc(iscsit_global.global_tsih_pool, + 1, VM_NOSLEEP | VM_NEXTFIT); + + /* ISCSI_UNSPEC_TSIH (0) indicates failure */ + if (result > ISCSI_MAX_TSIH) { + vmem_free(iscsit_global.global_tsih_pool, (void *)result, 1); + result = ISCSI_UNSPEC_TSIH; + } + + return ((uint16_t)result); +} + +static void +iscsit_tsih_free(uint16_t tsih) +{ + vmem_free(iscsit_global.global_tsih_pool, (void *)(uintptr_t)tsih, 1); +} + + +iscsit_sess_t * +iscsit_sess_create(iscsit_tgt_t *tgt, iscsit_conn_t *ict, + uint32_t cmdsn, uint8_t *isid, uint16_t tag, + char *initiator_name, char *target_name, + uint8_t *error_class, uint8_t *error_detail) +{ + iscsit_sess_t *result; + + + /* + * Even if this session create "fails" for some reason we still need + * to return a valid session pointer so that we can send the failed + * login response. + */ + result = kmem_zalloc(sizeof (*result), KM_SLEEP); + + /* Allocate TSIH */ + if ((result->ist_tsih = iscsit_tsih_alloc()) == ISCSI_UNSPEC_TSIH) { + /* Out of TSIH's */ + *error_class = ISCSI_STATUS_CLASS_TARGET_ERR; + *error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + /* + * Continue initializing this session so we can use it + * to complete the login process. + */ + } + + idm_sm_audit_init(&result->ist_state_audit); + rw_init(&result->ist_sn_rwlock, NULL, RW_DRIVER, NULL); + mutex_init(&result->ist_mutex, NULL, MUTEX_DEFAULT, NULL); + cv_init(&result->ist_cv, NULL, CV_DEFAULT, NULL); + list_create(&result->ist_events, sizeof (sess_event_ctx_t), + offsetof(sess_event_ctx_t, se_ctx_node)); + list_create(&result->ist_conn_list, sizeof (iscsit_conn_t), + offsetof(iscsit_conn_t, ict_sess_ln)); + + result->ist_state = SS_Q1_FREE; + result->ist_last_state = SS_Q1_FREE; + bcopy(isid, result->ist_isid, ISCSI_ISID_LEN); + result->ist_tpgt_tag = tag; + + result->ist_tgt = tgt; + result->ist_expcmdsn = cmdsn + 1; + result->ist_maxcmdsn = result->ist_expcmdsn + 1; + + result->ist_initiator_name = + kmem_alloc(strlen(initiator_name) + 1, KM_SLEEP); + (void) strcpy(result->ist_initiator_name, initiator_name); + if (target_name) { + /* A discovery session might not have a target name */ + result->ist_target_name = + kmem_alloc(strlen(target_name) + 1, KM_SLEEP); + (void) strcpy(result->ist_target_name, target_name); + } + idm_refcnt_init(&result->ist_refcnt, result); + + /* Login code will fill in ist_stmf_sess if necessary */ + + /* Kick session state machine (also binds connection to session) */ + iscsit_sess_sm_event(result, SE_CONN_IN_LOGIN, ict); + + *error_class = ISCSI_STATUS_CLASS_SUCCESS; + /* + * As noted above we must return a session pointer even if something + * failed. The resources will get freed later. + */ + return (result); +} + +static void +iscsit_sess_unref(void *ist_void) +{ + iscsit_sess_t *ist = ist_void; + + /* + * State machine has run to completion, destroy session + * + * If we have an associated STMF session we should clean it + * up now. + * + * This session is no longer associated with a target at this + * point so don't touch the target. + */ + mutex_enter(&ist->ist_mutex); + ASSERT(ist->ist_conn_count == 0); + if (ist->ist_stmf_sess != NULL) { + stmf_deregister_scsi_session(ist->ist_lport, + ist->ist_stmf_sess); + kmem_free(ist->ist_stmf_sess->ss_rport_id, + sizeof (scsi_devid_desc_t) + + strlen(ist->ist_initiator_name) + 1); + stmf_free(ist->ist_stmf_sess); + } + mutex_exit(&ist->ist_mutex); + + iscsit_sess_destroy(ist); +} + +void +iscsit_sess_destroy(iscsit_sess_t *ist) +{ + idm_refcnt_destroy(&ist->ist_refcnt); + if (ist->ist_initiator_name) + kmem_free(ist->ist_initiator_name, + strlen(ist->ist_initiator_name) + 1); + if (ist->ist_initiator_alias) + kmem_free(ist->ist_initiator_alias, + strlen(ist->ist_initiator_alias) + 1); + if (ist->ist_target_name) + kmem_free(ist->ist_target_name, + strlen(ist->ist_target_name) + 1); + if (ist->ist_target_alias) + kmem_free(ist->ist_target_alias, + strlen(ist->ist_target_alias) + 1); + list_destroy(&ist->ist_conn_list); + list_destroy(&ist->ist_events); + cv_destroy(&ist->ist_cv); + mutex_destroy(&ist->ist_mutex); + rw_destroy(&ist->ist_sn_rwlock); + kmem_free(ist, sizeof (*ist)); +} + +void +iscsit_sess_close(iscsit_sess_t *ist) +{ + iscsit_conn_t *ict; + + mutex_enter(&ist->ist_mutex); + /* + * Note in the session state that we are forcing this session + * to close so that the session state machine can avoid + * pointless delays like transitions to SS_Q4_FAILED state. + */ + ist->ist_admin_close = B_TRUE; + if (ist->ist_state == SS_Q3_LOGGED_IN) { + for (ict = list_head(&ist->ist_conn_list); + ict != NULL; + ict = list_next(&ist->ist_conn_list, ict)) { + iscsit_send_async_event(ict, + ISCSI_ASYNC_EVENT_REQUEST_LOGOUT); + } + } + mutex_exit(&ist->ist_mutex); +} + + +void +iscsit_sess_bind_conn(iscsit_sess_t *ist, iscsit_conn_t *ict) +{ + iscsit_conn_hold(ict); + iscsit_sess_hold(ist); + ict->ict_sess = ist; + mutex_enter(&ist->ist_mutex); + ist->ist_conn_count++; + list_insert_tail(&ist->ist_conn_list, ict); + mutex_exit(&ist->ist_mutex); +} + +void +iscsit_sess_unbind_conn(iscsit_sess_t *ist, iscsit_conn_t *ict) +{ + mutex_enter(&ist->ist_mutex); + list_remove(&ist->ist_conn_list, ict); + ist->ist_conn_count--; + mutex_exit(&ist->ist_mutex); + iscsit_sess_rele(ist); + iscsit_conn_rele(ict); +} + +void +iscsit_sess_hold(iscsit_sess_t *ist) +{ + idm_refcnt_hold(&ist->ist_refcnt); +} + +void +iscsit_sess_rele(iscsit_sess_t *ist) +{ + idm_refcnt_rele(&ist->ist_refcnt); +} + +iscsit_conn_t * +iscsit_sess_lookup_conn(iscsit_sess_t *ist, uint16_t cid) +{ + iscsit_conn_t *result; + + mutex_enter(&ist->ist_mutex); + for (result = list_head(&ist->ist_conn_list); + result != NULL; + result = list_next(&ist->ist_conn_list, result)) { + if (result->ict_cid == cid) { + iscsit_conn_hold(result); + mutex_exit(&ist->ist_mutex); + return (result); + } + } + mutex_exit(&ist->ist_mutex); + + return (NULL); +} + +iscsit_sess_t * +iscsit_sess_reinstate(iscsit_tgt_t *tgt, iscsit_sess_t *ist, iscsit_conn_t *ict, + uint8_t *error_class, uint8_t *error_detail) +{ + iscsit_sess_t *new_sess; + + mutex_enter(&ist->ist_mutex); + + /* + * Session reinstatement replaces a current session with a new session. + * The new session will have the same ISID as the existing session. + */ + new_sess = iscsit_sess_create(tgt, ict, 0, + ist->ist_isid, ist->ist_tpgt_tag, + ist->ist_initiator_name, ist->ist_target_name, + error_class, error_detail); + ASSERT(new_sess != NULL); + + /* Copy additional fields from original session */ + new_sess->ist_expcmdsn = ist->ist_expcmdsn; + new_sess->ist_maxcmdsn = ist->ist_expcmdsn + 1; + + if (ist->ist_state != SS_Q6_DONE && + ist->ist_state != SS_Q7_ERROR) { + /* + * Generate reinstate event + */ + sess_sm_event_locked(ist, SE_SESSION_REINSTATE, NULL); + } + mutex_exit(&ist->ist_mutex); + + return (new_sess); +} + +int +iscsit_sess_avl_compare(const void *void_sess1, const void *void_sess2) +{ + const iscsit_sess_t *sess1 = void_sess1; + const iscsit_sess_t *sess2 = void_sess2; + int result; + + /* + * Sort by initiator name, then ISID then portal group tag + */ + result = strcmp(sess1->ist_initiator_name, sess2->ist_initiator_name); + if (result < 0) { + return (-1); + } else if (result > 0) { + return (1); + } + + /* + * Initiator names match, compare ISIDs + */ + result = memcmp(sess1->ist_isid, sess2->ist_isid, ISCSI_ISID_LEN); + if (result < 0) { + return (-1); + } else if (result > 0) { + return (1); + } + + /* + * ISIDs match, compare portal group tags + */ + if (sess1->ist_tpgt_tag < sess2->ist_tpgt_tag) { + return (-1); + } else if (sess1->ist_tpgt_tag > sess2->ist_tpgt_tag) { + return (1); + } + + /* + * Portal group tags match, compare TSIHs + */ + if (sess1->ist_tsih < sess2->ist_tsih) { + return (-1); + } else if (sess1->ist_tsih > sess2->ist_tsih) { + return (1); + } + + /* + * Sessions match + */ + return (0); +} + + +/* + * State machine + */ + +void +iscsit_sess_sm_event(iscsit_sess_t *ist, iscsit_session_event_t event, + iscsit_conn_t *ict) +{ + mutex_enter(&ist->ist_mutex); + sess_sm_event_locked(ist, event, ict); + mutex_exit(&ist->ist_mutex); +} + +static void +sess_sm_event_locked(iscsit_sess_t *ist, iscsit_session_event_t event, + iscsit_conn_t *ict) +{ + sess_event_ctx_t *ctx; + + iscsit_sess_hold(ist); + + ctx = kmem_zalloc(sizeof (*ctx), KM_SLEEP); + + ctx->se_ctx_event = event; + ctx->se_event_data = ict; + + list_insert_tail(&ist->ist_events, ctx); + /* + * Use the icl_busy flag to keep the state machine single threaded. + * This also serves as recursion avoidance since this flag will + * always be set if we call login_sm_event from within the + * state machine code. + */ + if (!ist->ist_sm_busy) { + ist->ist_sm_busy = B_TRUE; + while (!list_is_empty(&ist->ist_events)) { + ctx = list_head(&ist->ist_events); + list_remove(&ist->ist_events, ctx); + idm_sm_audit_event(&ist->ist_state_audit, + SAS_ISCSIT_SESS, (int)ist->ist_state, + (int)ctx->se_ctx_event, (uintptr_t)ict); + mutex_exit(&ist->ist_mutex); + sess_sm_event_dispatch(ist, ctx); + mutex_enter(&ist->ist_mutex); + } + ist->ist_sm_busy = B_FALSE; + + } + + iscsit_sess_rele(ist); +} + +static void +sess_sm_event_dispatch(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + iscsit_conn_t *ict; + + DTRACE_PROBE2(session__event, iscsit_sess_t *, ist, + sess_event_ctx_t *, ctx); + + IDM_SM_LOG(CE_NOTE, "sess_sm_event_dispatch: sess %p event %s(%d)", + (void *)ist, iscsit_se_name[ctx->se_ctx_event], ctx->se_ctx_event); + + /* State independent actions */ + switch (ctx->se_ctx_event) { + case SE_CONN_IN_LOGIN: + ict = ctx->se_event_data; + iscsit_sess_bind_conn(ist, ict); + break; + case SE_CONN_FAIL: + ict = ctx->se_event_data; + iscsit_sess_unbind_conn(ist, ict); + break; + } + + /* State dependent actions */ + switch (ist->ist_state) { + case SS_Q1_FREE: + sess_sm_q1_free(ist, ctx); + break; + case SS_Q2_ACTIVE: + sess_sm_q2_active(ist, ctx); + break; + case SS_Q3_LOGGED_IN: + sess_sm_q3_logged_in(ist, ctx); + break; + case SS_Q4_FAILED: + sess_sm_q4_failed(ist, ctx); + break; + case SS_Q5_CONTINUE: + sess_sm_q5_continue(ist, ctx); + break; + case SS_Q6_DONE: + sess_sm_q6_done(ist, ctx); + break; + case SS_Q7_ERROR: + sess_sm_q7_error(ist, ctx); + break; + default: + ASSERT(0); + break; + } + + kmem_free(ctx, sizeof (*ctx)); +} + +static void +sess_sm_q1_free(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + switch (ctx->se_ctx_event) { + case SE_CONN_IN_LOGIN: + /* N1 */ + sess_sm_new_state(ist, ctx, SS_Q2_ACTIVE); + break; + default: + ASSERT(0); + break; + } +} + + +static void +sess_sm_q2_active(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + switch (ctx->se_ctx_event) { + case SE_CONN_LOGGED_IN: + /* N2 track FFP connections */ + ist->ist_ffp_conn_count++; + sess_sm_new_state(ist, ctx, SS_Q3_LOGGED_IN); + break; + case SE_CONN_IN_LOGIN: + /* N2.1, don't care stay in this state */ + break; + case SE_CONN_FAIL: + /* N9 */ + sess_sm_new_state(ist, ctx, SS_Q7_ERROR); + break; + case SE_SESSION_REINSTATE: + /* N11 */ + sess_sm_new_state(ist, ctx, SS_Q6_DONE); + break; + default: + ASSERT(0); + break; + } +} + +static void +sess_sm_q3_logged_in(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + iscsit_conn_t *ict; + + switch (ctx->se_ctx_event) { + case SE_CONN_IN_LOGIN: + case SE_CONN_FAIL: + /* N2.2, don't care */ + break; + case SE_CONN_LOGGED_IN: + /* N2.2, track FFP connections */ + ist->ist_ffp_conn_count++; + break; + case SE_CONN_FFP_FAIL: + case SE_CONN_FFP_DISABLE: + /* + * Event data from event context is the associated connection + * which in this case happens to be the last FFP connection + * for the session. In certain cases we need to refer + * to this last valid connection (i.e. RFC3720 section 12.16) + * so we'll save off a pointer here for later use. + */ + ASSERT(ist->ist_ffp_conn_count >= 1); + ist->ist_failed_conn = (iscsit_conn_t *)ctx->se_event_data; + ist->ist_ffp_conn_count--; + if (ist->ist_ffp_conn_count == 0) { + /* + * N5(fail) or N3(disable) + * + * If the event is SE_CONN_FFP_FAIL but we are + * in the midst of an administrative session close + * because of a service or target offline then + * there is no need to go to "failed" state. + */ + sess_sm_new_state(ist, ctx, + ((ctx->se_ctx_event == SE_CONN_FFP_DISABLE) || + (ist->ist_admin_close)) ? + SS_Q6_DONE : SS_Q4_FAILED); + } + break; + case SE_SESSION_CLOSE: + case SE_SESSION_REINSTATE: + /* N3 */ + mutex_enter(&ist->ist_mutex); + if (ctx->se_ctx_event == SE_SESSION_CLOSE) { + ASSERT(ist->ist_ffp_conn_count >= 1); + ist->ist_ffp_conn_count--; + } + for (ict = list_head(&ist->ist_conn_list); + ict != NULL; + ict = list_next(&ist->ist_conn_list, ict)) { + if ((ctx->se_ctx_event == SE_SESSION_CLOSE) && + ((iscsit_conn_t *)ctx->se_event_data == ict)) { + /* + * Skip this connection since it will + * see the logout response + */ + continue; + } + idm_conn_event(ict->ict_ic, CE_LOGOUT_SESSION_SUCCESS, + NULL); + } + mutex_exit(&ist->ist_mutex); + + sess_sm_new_state(ist, ctx, SS_Q6_DONE); + break; + default: + ASSERT(0); + break; + } +} + +static void +sess_sm_timeout(void *arg) +{ + iscsit_sess_t *ist = arg; + + iscsit_sess_sm_event(ist, SE_SESSION_TIMEOUT, NULL); +} + +static void +sess_sm_q4_failed(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + /* Session timer must not be running when we leave this event */ + switch (ctx->se_ctx_event) { + case SE_CONN_IN_LOGIN: + /* N7 */ + sess_sm_new_state(ist, ctx, SS_Q5_CONTINUE); + break; + case SE_SESSION_REINSTATE: + /* N6 */ + (void) untimeout(ist->ist_state_timeout); + /*FALLTHROUGH*/ + case SE_SESSION_TIMEOUT: + /* N6 */ + sess_sm_new_state(ist, ctx, SS_Q6_DONE); + break; + case SE_CONN_FAIL: + /* Don't care */ + break; + default: + ASSERT(0); + break; + } +} + +static void +sess_sm_q5_continue(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + switch (ctx->se_ctx_event) { + case SE_CONN_FAIL: + /* N5 */ + sess_sm_new_state(ist, ctx, SS_Q4_FAILED); + break; + case SE_CONN_LOGGED_IN: + /* N10 */ + sess_sm_new_state(ist, ctx, SS_Q3_LOGGED_IN); + break; + case SE_SESSION_REINSTATE: + /* N11 */ + sess_sm_new_state(ist, ctx, SS_Q6_DONE); + break; + default: + ASSERT(0); + break; + } +} + +static void +sess_sm_q6_done(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + /* Terminal state */ + switch (ctx->se_ctx_event) { + case SE_CONN_FFP_FAIL: + case SE_CONN_FFP_DISABLE: + ASSERT(ist->ist_ffp_conn_count >= 1); + ist->ist_ffp_conn_count--; + break; + case SE_CONN_FAIL: + if (ist->ist_conn_count == 0) { + idm_refcnt_async_wait_ref(&ist->ist_refcnt, + &iscsit_sess_unref); + } + break; + default: + break; + } +} + +static void +sess_sm_q7_error(iscsit_sess_t *ist, sess_event_ctx_t *ctx) +{ + /* Terminal state */ + switch (ctx->se_ctx_event) { + case SE_CONN_FAIL: + if (ist->ist_conn_count == 0) { + idm_refcnt_async_wait_ref(&ist->ist_refcnt, + &iscsit_sess_unref); + } + break; + default: + break; + } +} + +static void +sess_sm_new_state(iscsit_sess_t *ist, sess_event_ctx_t *ctx, + iscsit_session_state_t new_state) +{ + int t2r_secs; + + /* + * Validate new state + */ + ASSERT(new_state != SS_UNDEFINED); + ASSERT3U(new_state, <, SS_MAX_STATE); + + new_state = (new_state < SS_MAX_STATE) ? + new_state : SS_UNDEFINED; + + IDM_SM_LOG(CE_NOTE, "sess_sm_new_state: sess %p, evt %s(%d), " + "%s(%d) --> %s(%d)\n", (void *) ist, + iscsit_se_name[ctx->se_ctx_event], ctx->se_ctx_event, + iscsit_ss_name[ist->ist_state], ist->ist_state, + iscsit_ss_name[new_state], new_state); + + DTRACE_PROBE3(sess__state__change, + iscsit_sess_t *, ist, sess_event_ctx_t *, ctx, + iscsit_session_state_t, new_state); + + mutex_enter(&ist->ist_mutex); + idm_sm_audit_state_change(&ist->ist_state_audit, SAS_ISCSIT_SESS, + (int)ist->ist_state, (int)new_state); + ist->ist_last_state = ist->ist_state; + ist->ist_state = new_state; + mutex_exit(&ist->ist_mutex); + + switch (ist->ist_state) { + case SS_Q1_FREE: + break; + case SS_Q2_ACTIVE: + iscsit_tgt_bind_sess(ist->ist_tgt, ist); + break; + case SS_Q3_LOGGED_IN: + break; + case SS_Q4_FAILED: + t2r_secs = + ist->ist_failed_conn->ict_op.op_default_time_2_retain; + ist->ist_state_timeout = timeout(sess_sm_timeout, ist, + drv_usectohz(t2r_secs*1000000)); + break; + case SS_Q5_CONTINUE: + break; + case SS_Q6_DONE: + case SS_Q7_ERROR: + /* + * We won't need our TSIH anymore and it represents an + * implicit reference to the global TSIH pool. Get rid + * of it. + */ + if (ist->ist_tsih != ISCSI_UNSPEC_TSIH) { + iscsit_tsih_free(ist->ist_tsih); + } + + /* + * We don't want this session to show up anymore so unbind + * it now. After this call this session cannot have any + * references outside itself (implicit or explicit). + */ + iscsit_tgt_unbind_sess(ist->ist_tgt, ist); + + /* + * If we have more connections bound then more events + * are comming so don't wait for idle yet. + */ + if (ist->ist_conn_count == 0) { + idm_refcnt_async_wait_ref(&ist->ist_refcnt, + &iscsit_sess_unref); + } + break; + default: + ASSERT(0); + /*NOTREACHED*/ + } +} diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_text.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_text.c new file mode 100644 index 000000000000..48ef76cfb0aa --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_text.c @@ -0,0 +1,324 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IPADDRSTRLEN INET6_ADDRSTRLEN /* space for ipaddr string */ +#define PORTALSTRLEN (IPADDRSTRLEN+16) /* add space for :port,tag */ + +/* + * The kernel inet_ntop() function formats ipv4 address fields with + * leading zeros which the win2k initiator interprets as octal. + */ + +static void iscsit_v4_ntop(struct in_addr *in, char a[], int size) +{ + unsigned char *p = (unsigned char *) in; + + (void) snprintf(a, size, "%d.%d.%d.%d", *p, *(p+1), *(p+2), *(p+3)); +} + +static void +iscsit_send_reject(idm_pdu_t *req_pdu, uint8_t reason_code) +{ + idm_pdu_t *reject_pdu; + iscsi_reject_rsp_hdr_t *rej_hdr; + + reject_pdu = idm_pdu_alloc(sizeof (iscsi_hdr_t), req_pdu->isp_hdrlen); + if (reject_pdu == NULL) { + /* Just give up.. the initiator will timeout */ + idm_pdu_complete(req_pdu, IDM_STATUS_SUCCESS); + return; + } + + /* Payload contains the header from the bad PDU */ + idm_pdu_init(reject_pdu, req_pdu->isp_ic, NULL, NULL); + bcopy(req_pdu->isp_hdr, reject_pdu->isp_data, req_pdu->isp_hdrlen); + + rej_hdr = (iscsi_reject_rsp_hdr_t *)reject_pdu->isp_hdr; + bzero(rej_hdr, sizeof (*rej_hdr)); + rej_hdr->opcode = ISCSI_OP_REJECT_MSG; + rej_hdr->flags = ISCSI_FLAG_FINAL; + rej_hdr->reason = reason_code; + hton24(rej_hdr->dlength, req_pdu->isp_hdrlen); + rej_hdr->must_be_ff[0] = 0xff; + rej_hdr->must_be_ff[1] = 0xff; + rej_hdr->must_be_ff[2] = 0xff; + rej_hdr->must_be_ff[3] = 0xff; + + iscsit_pdu_tx(reject_pdu); + idm_pdu_complete(req_pdu, IDM_STATUS_SUCCESS); +} + +static void +iscsit_add_target_portals(nvlist_t *nv_resp, iscsit_tgt_t *target) +{ + iscsit_tpgt_t *tpg_list; + iscsit_tpg_t *tpg; + idm_addr_list_t *ipaddr_p; + idm_addr_t *tip; + iscsit_portal_t *portal; + int ipsize, i; + char *name = "TargetAddress"; + char a[IPADDRSTRLEN]; + char v[PORTALSTRLEN]; + struct sockaddr_storage *ss; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + struct in_addr *in; + struct in6_addr *in6; + int type; + + + /* + * Look through the portal groups associated with this target. + */ + mutex_enter(&target->target_mutex); + tpg_list = avl_first(&target->target_tpgt_list); + while (tpg_list != NULL) { + tpg = tpg_list->tpgt_tpg; + /* + * The default portal group will match any current interface. + * A target cannot listen on other portal groups if it + * listens on the default portal group. + */ + if (tpg == iscsit_global.global_default_tpg) { + /* + * get the list of plumbed interfaces + */ + ipsize = idm_get_ipaddr(&ipaddr_p); + if (ipsize == 0) { + mutex_exit(&target->target_mutex); + return; + } + tip = &ipaddr_p->al_addrs[0]; + for (i = 0; i < ipaddr_p->al_out_cnt; i++, tip++) { + if (tip->a_addr.i_insize == + sizeof (struct in_addr)) { + type = AF_INET; + in = &tip->a_addr.i_addr.in4; + iscsit_v4_ntop(in, a, sizeof (a)); + (void) snprintf(v, sizeof (v), + "%s,1", a); + } else if (tip->a_addr.i_insize == + sizeof (struct in6_addr)) { + type = AF_INET6; + in6 = &tip->a_addr.i_addr.in6; + (void) inet_ntop(type, in6, a, + sizeof (a)); + (void) snprintf(v, sizeof (v), + "[%s],1", a); + } else { + break; + } + /* + * Add the TargetAddress= nvpair + */ + (void) nvlist_add_string(nv_resp, name, v); + } + kmem_free(ipaddr_p, ipsize); + /* + * Cannot listen on other portal groups. + */ + mutex_exit(&target->target_mutex); + return; + } + /* + * Found a defined portal group - add each portal address. + */ + portal = avl_first(&tpg->tpg_portal_list); + while (portal != NULL) { + ss = &portal->portal_addr; + type = ss->ss_family; + switch (type) { + case AF_INET: + sin = (struct sockaddr_in *)ss; + in = &sin->sin_addr; + iscsit_v4_ntop(in, a, sizeof (a)); + (void) snprintf(v, sizeof (v), "%s:%d,%d", a, + ntohs(sin->sin_port), + tpg_list->tpgt_tag); + (void) nvlist_add_string(nv_resp, name, v); + break; + case AF_INET6: + sin6 = (struct sockaddr_in6 *)ss; + in6 = &sin6->sin6_addr; + (void) inet_ntop(type, in6, a, sizeof (a)); + (void) snprintf(v, sizeof (v), "[%s]:%d,%d", a, + sin6->sin6_port, + tpg_list->tpgt_tag); + (void) nvlist_add_string(nv_resp, name, v); + break; + default: + break; + } + portal = AVL_NEXT(&tpg->tpg_portal_list, portal); + } + tpg_list = AVL_NEXT(&target->target_tpgt_list, tpg_list); + } + mutex_exit(&target->target_mutex); +} + +void +iscsit_pdu_op_text_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu) +{ + iscsi_text_hdr_t *th_req = (iscsi_text_hdr_t *)rx_pdu->isp_hdr; + iscsi_text_rsp_hdr_t *th_resp; + nvlist_t *nv_resp; + char *textbuf; + char *kv_name, *kv_pair; + int flags; + int textbuflen; + int rc; + idm_pdu_t *resp; + + flags = th_req->flags; + if ((flags & ISCSI_FLAG_FINAL) != ISCSI_FLAG_FINAL) { + /* Cannot handle multi-PDU messages now */ + iscsit_send_reject(rx_pdu, ISCSI_REJECT_CMD_NOT_SUPPORTED); + return; + } + if (th_req->ttt != ISCSI_RSVD_TASK_TAG) { + /* Last of a multi-PDU message */ + iscsit_send_reject(rx_pdu, ISCSI_REJECT_CMD_NOT_SUPPORTED); + return; + } + + /* + * At this point we have a single PDU text command + */ + + textbuf = (char *)rx_pdu->isp_data; + textbuflen = rx_pdu->isp_datalen; + kv_name = "SendTargets="; + kv_pair = "SendTargets=All"; + if (strncmp(kv_name, textbuf, strlen(kv_name)) != 0) { + /* Not a Sendtargets command */ + iscsit_send_reject(rx_pdu, ISCSI_REJECT_CMD_NOT_SUPPORTED); + return; + } + if (strcmp(kv_pair, textbuf) == 0 && + ict->ict_op.op_discovery_session == B_TRUE) { + iscsit_tgt_t *target; + int validlen; + + /* + * Most common case of SendTargets=All during discovery. + */ + /* + * Create an nvlist for response. + */ + if (nvlist_alloc(&nv_resp, 0, KM_SLEEP) != 0) { + iscsit_send_reject(rx_pdu, + ISCSI_REJECT_CMD_NOT_SUPPORTED); + return; + } + + ISCSIT_GLOBAL_LOCK(RW_READER); + target = avl_first(&iscsit_global.global_target_list); + while (target != NULL) { + char *name = "TargetName"; + char *val = target->target_name; + + (void) nvlist_add_string(nv_resp, name, val); + iscsit_add_target_portals(nv_resp, target); + target = AVL_NEXT(&iscsit_global.global_target_list, + target); + } + ISCSIT_GLOBAL_UNLOCK(); + + /* + * Convert the reponse nv list into text buffer. + */ + textbuf = 0; + textbuflen = 0; + validlen = 0; + rc = idm_nvlist_to_textbuf(nv_resp, &textbuf, + &textbuflen, &validlen); + nvlist_free(nv_resp); + if (rc != 0) { + if (textbuf && textbuflen) + kmem_free(textbuf, textbuflen); + iscsit_send_reject(rx_pdu, + ISCSI_REJECT_CMD_NOT_SUPPORTED); + return; + } + /* + * Allocate a PDU and copy in text response buffer + */ + resp = idm_pdu_alloc(sizeof (iscsi_hdr_t), validlen); + idm_pdu_init(resp, ict->ict_ic, NULL, NULL); + bcopy(textbuf, resp->isp_data, validlen); + kmem_free(textbuf, textbuflen); + /* + * Fill in the response header + */ + th_resp = (iscsi_text_rsp_hdr_t *)resp->isp_hdr; + bzero(th_resp, sizeof (*th_resp)); + th_resp->opcode = ISCSI_OP_TEXT_RSP; + th_resp->flags = ISCSI_FLAG_FINAL; + th_resp->ttt = ISCSI_RSVD_TASK_TAG; + th_resp->itt = th_req->itt; + hton24(th_resp->dlength, validlen); + } else { + /* + * Other cases to handle + * Discovery session: + * SendTargets= + * Normal session + * SendTargets= - should match session + * SendTargets= - assume target name of session + * All others + * Error + */ + iscsit_send_reject(rx_pdu, ISCSI_REJECT_CMD_NOT_SUPPORTED); + return; + } + + /* Send the response on its way */ + iscsit_pdu_tx(resp); + idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS); +} diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_tgt.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_tgt.c new file mode 100644 index 000000000000..8d42eccedb17 --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_tgt.c @@ -0,0 +1,2054 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#define ISCSIT_TGT_SM_STRINGS +#include +#include + +typedef struct { + list_node_t te_ctx_node; + iscsit_tgt_event_t te_ctx_event; +} tgt_event_ctx_t; + +static void +tgt_sm_event_dispatch(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx); + +static void +tgt_sm_created(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx); + +static void +tgt_sm_onlining(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx); + +static void +tgt_sm_online(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx); + +static void +tgt_sm_stmf_online(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx); + +static void +tgt_sm_deleting_need_offline(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx); + +static void +tgt_sm_offlining(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx); + +static void +tgt_sm_offline(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx); + +static void +tgt_sm_stmf_offline(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx); + +static void +tgt_sm_deleting_stmf_dereg(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx); + +static void +tgt_sm_deleting_stmf_dereg_fail(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx); + +static void +tgt_sm_deleting(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx); + +static void +iscsit_tgt_dereg_retry(void *arg); + +static void +iscsit_tgt_dereg_task(void *arg); + +static void +tgt_sm_new_state(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx, + iscsit_tgt_state_t new_state); + + +static iscsit_tgt_t * +iscsit_tgt_create(it_tgt_t *cfg_tgt); + +static void +iscsit_tgt_unref(void *tgt); + +static void +iscsit_tgt_async_wait_ref(iscsit_tgt_t *tgt, idm_refcnt_cb_t *cb_func); + +static void +iscsit_tgt_destroy(iscsit_tgt_t *tgt); + +static iscsit_tpgt_t * +iscsit_tgt_lookup_tpgt_locked(iscsit_tgt_t *tgt, uint16_t tag); + +static iscsit_tpg_t * +iscsit_tpg_lookup_locked(char *tpg_name); + +static iscsit_portal_t * +iscsit_tpg_portal_lookup_locked(iscsit_tpg_t *tpg, + struct sockaddr_storage *sa); + +static idm_status_t +iscsit_tgt_online(iscsit_tgt_t *tgt); + +static void +iscsit_tgt_offline(iscsit_tgt_t *tgt); + +static idm_status_t +iscsit_tgt_modify(iscsit_tgt_t *tgt, it_tgt_t *cfg_tgt); + +static idm_status_t +iscsit_tgt_merge_tpgt(iscsit_tgt_t *tgt, it_tgt_t *cfg_tgt, + list_t *tpgt_del_list); + +static iscsit_tpgt_t * +iscsit_tpgt_create(it_tpgt_t *cfg_tpgt); + +static iscsit_tpgt_t * +iscsit_tpgt_create_default(); + +static void +iscsit_tpgt_destroy(iscsit_tpgt_t *tpgt); + +static iscsit_tpg_t * +iscsit_tpg_create(it_tpg_t *tpg); + +static void +iscsit_tpg_modify(iscsit_tpg_t *tpg, it_tpg_t *cfg_tpg); + +static void +iscsit_tpg_destroy(iscsit_tpg_t *tpg); + +static iscsit_portal_t * +iscsit_portal_create(iscsit_tpg_t *tpg, struct sockaddr_storage *sa); + +static void +iscsit_portal_destroy(iscsit_portal_t *portal); + +static idm_status_t +iscsit_portal_online(iscsit_portal_t *portal); + +static void +iscsit_portal_offline(iscsit_portal_t *portal); + + + +/* + * Target state machine + */ + +void +iscsit_tgt_sm_event(iscsit_tgt_t *tgt, iscsit_tgt_event_t event) +{ + mutex_enter(&tgt->target_mutex); + tgt_sm_event_locked(tgt, event); + mutex_exit(&tgt->target_mutex); +} + +void +tgt_sm_event_locked(iscsit_tgt_t *tgt, iscsit_tgt_event_t event) +{ + tgt_event_ctx_t *ctx; + + iscsit_tgt_hold(tgt); + + ctx = kmem_zalloc(sizeof (*ctx), KM_SLEEP); + + ctx->te_ctx_event = event; + + list_insert_tail(&tgt->target_events, ctx); + /* + * Use the icl_busy flag to keep the state machine single threaded. + * This also serves as recursion avoidance since this flag will + * always be set if we call iscsit_tgt_sm_event from within the + * state machine code. + */ + if (!tgt->target_sm_busy) { + tgt->target_sm_busy = B_TRUE; + while (!list_is_empty(&tgt->target_events)) { + ctx = list_head(&tgt->target_events); + list_remove(&tgt->target_events, ctx); + idm_sm_audit_event(&tgt->target_state_audit, + SAS_ISCSIT_TGT, (int)tgt->target_state, + (int)ctx->te_ctx_event, 0); + mutex_exit(&tgt->target_mutex); + tgt_sm_event_dispatch(tgt, ctx); + mutex_enter(&tgt->target_mutex); + } + tgt->target_sm_busy = B_FALSE; + + } + + iscsit_tgt_rele(tgt); +} + +static void +tgt_sm_event_dispatch(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx) +{ + DTRACE_PROBE2(tgt__event, iscsit_tgt_t *, tgt, + tgt_event_ctx_t *, ctx); + + IDM_SM_LOG(CE_NOTE, "tgt_sm_event_dispatch: tgt %p event %s(%d)", + (void *)tgt, iscsit_te_name[ctx->te_ctx_event], ctx->te_ctx_event); + + /* State independent actions */ + switch (ctx->te_ctx_event) { + case TE_DELETE: + tgt->target_deleting = B_TRUE; + break; + } + + /* State dependent actions */ + switch (tgt->target_state) { + case TS_CREATED: + tgt_sm_created(tgt, ctx); + break; + case TS_ONLINING: + tgt_sm_onlining(tgt, ctx); + break; + case TS_ONLINE: + tgt_sm_online(tgt, ctx); + break; + case TS_STMF_ONLINE: + tgt_sm_stmf_online(tgt, ctx); + break; + case TS_DELETING_NEED_OFFLINE: + tgt_sm_deleting_need_offline(tgt, ctx); + break; + case TS_OFFLINING: + tgt_sm_offlining(tgt, ctx); + break; + case TS_OFFLINE: + tgt_sm_offline(tgt, ctx); + break; + case TS_STMF_OFFLINE: + tgt_sm_stmf_offline(tgt, ctx); + break; + case TS_DELETING_STMF_DEREG: + tgt_sm_deleting_stmf_dereg(tgt, ctx); + break; + case TS_DELETING_STMF_DEREG_FAIL: + tgt_sm_deleting_stmf_dereg_fail(tgt, ctx); + break; + case TS_DELETING: + tgt_sm_deleting(tgt, ctx); + break; + default: + ASSERT(0); + } + + kmem_free(ctx, sizeof (*ctx)); +} + +static void +tgt_sm_created(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx) +{ + stmf_change_status_t scs; + + switch (ctx->te_ctx_event) { + case TE_STMF_ONLINE_REQ: + tgt_sm_new_state(tgt, ctx, TS_ONLINING); + break; + case TE_DELETE: + tgt_sm_new_state(tgt, ctx, TS_DELETING_STMF_DEREG); + break; + case TE_STMF_OFFLINE_REQ: + /* + * We're already offline but update to an equivelant + * state just to note that STMF talked to us. + */ + scs.st_completion_status = STMF_SUCCESS; + scs.st_additional_info = NULL; + tgt_sm_new_state(tgt, ctx, TS_OFFLINE); + (void) stmf_ctl(STMF_CMD_LPORT_OFFLINE_COMPLETE, + tgt->target_stmf_lport, &scs); + break; + case TE_STMF_ONLINE_COMPLETE_ACK: + case TE_STMF_OFFLINE_COMPLETE_ACK: + /* Ignore */ + break; + default: + ASSERT(0); + } +} + +static void +tgt_sm_onlining(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx) +{ + stmf_change_status_t scs; + + switch (ctx->te_ctx_event) { + case TE_ONLINE_SUCCESS: + tgt_sm_new_state(tgt, ctx, TS_ONLINE); + break; + case TE_ONLINE_FAIL: + tgt_sm_new_state(tgt, ctx, TS_STMF_OFFLINE); + break; + case TE_DELETE: + /* TE_DELETE is handled in tgt_sm_event_dispatch() */ + break; + case TE_STMF_ONLINE_REQ: + case TE_STMF_OFFLINE_REQ: + /* + * We can't complete STMF's request since we are busy going + * online. + */ + scs.st_completion_status = STMF_INVALID_ARG; + scs.st_additional_info = NULL; + (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ? + STMF_CMD_LPORT_ONLINE_COMPLETE : + STMF_CMD_LPORT_OFFLINE_COMPLETE, + tgt->target_stmf_lport, &scs); + break; + case TE_STMF_ONLINE_COMPLETE_ACK: + case TE_STMF_OFFLINE_COMPLETE_ACK: + /* Ignore */ + break; + default: + ASSERT(0); + } +} + +static void +tgt_sm_online(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx) +{ + stmf_change_status_t scs; + + switch (ctx->te_ctx_event) { + case TE_STMF_ONLINE_COMPLETE_ACK: + if (tgt->target_deleting) { + tgt_sm_new_state(tgt, ctx, TS_DELETING_NEED_OFFLINE); + } else { + tgt_sm_new_state(tgt, ctx, TS_STMF_ONLINE); + } + break; + case TE_DELETE: + /* TE_DELETE is handled in tgt_sm_event_dispatch() */ + break; + case TE_STMF_ONLINE_REQ: + case TE_STMF_OFFLINE_REQ: + /* + * We can't complete STMF's request since we are busy going + * online (waiting for acknowlegement from STMF) + */ + scs.st_completion_status = STMF_INVALID_ARG; + scs.st_additional_info = NULL; + (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ? + STMF_CMD_LPORT_ONLINE_COMPLETE : + STMF_CMD_LPORT_OFFLINE_COMPLETE, + tgt->target_stmf_lport, &scs); + break; + case TE_STMF_OFFLINE_COMPLETE_ACK: + /* Ignore */ + break; + default: + ASSERT(0); + } +} + + +static void +tgt_sm_stmf_online(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx) +{ + stmf_change_status_t scs; + + /* Deregister target with iSNS whenever we leave this state */ + + switch (ctx->te_ctx_event) { + case TE_DELETE: + (void) iscsit_isns_deregister(tgt); + tgt_sm_new_state(tgt, ctx, TS_DELETING_NEED_OFFLINE); + break; + case TE_STMF_OFFLINE_REQ: + (void) iscsit_isns_deregister(tgt); + tgt_sm_new_state(tgt, ctx, TS_OFFLINING); + break; + case TE_STMF_ONLINE_REQ: + /* Already online */ + scs.st_completion_status = STMF_ALREADY; + scs.st_additional_info = NULL; + (void) stmf_ctl(STMF_CMD_LPORT_ONLINE_COMPLETE, + tgt->target_stmf_lport, &scs); + break; + case TE_STMF_ONLINE_COMPLETE_ACK: + case TE_STMF_OFFLINE_COMPLETE_ACK: + /* Ignore */ + break; + default: + ASSERT(0); + } +} + + +static void +tgt_sm_deleting_need_offline(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx) +{ + stmf_change_status_t scs; + + switch (ctx->te_ctx_event) { + case TE_STMF_OFFLINE_REQ: + tgt_sm_new_state(tgt, ctx, TS_OFFLINING); + break; + case TE_DELETE: + /* TE_DELETE is handled in tgt_sm_event_dispatch() */ + break; + case TE_STMF_ONLINE_REQ: + /* + * We can't complete STMF's request since we need to be offlined + */ + scs.st_completion_status = STMF_INVALID_ARG; + scs.st_additional_info = NULL; + (void) stmf_ctl(STMF_CMD_LPORT_ONLINE_COMPLETE, + tgt->target_stmf_lport, &scs); + break; + case TE_STMF_ONLINE_COMPLETE_ACK: + case TE_STMF_OFFLINE_COMPLETE_ACK: + /* Ignore */ + break; + default: + ASSERT(0); + } +} + + +static void +tgt_sm_offlining(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx) +{ + stmf_change_status_t scs; + + switch (ctx->te_ctx_event) { + case TE_OFFLINE_COMPLETE: + tgt_sm_new_state(tgt, ctx, TS_OFFLINE); + break; + case TE_DELETE: + /* TE_DELETE is handled in tgt_sm_event_dispatch() */ + break; + case TE_STMF_ONLINE_REQ: + case TE_STMF_OFFLINE_REQ: + /* + * We can't complete STMF's request since we are busy going + * offline. + */ + scs.st_completion_status = STMF_INVALID_ARG; + scs.st_additional_info = NULL; + (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ? + STMF_CMD_LPORT_ONLINE_COMPLETE : + STMF_CMD_LPORT_OFFLINE_COMPLETE, + tgt->target_stmf_lport, &scs); + break; + case TE_STMF_ONLINE_COMPLETE_ACK: + case TE_STMF_OFFLINE_COMPLETE_ACK: + /* Ignore */ + break; + default: + ASSERT(0); + } +} + + +static void +tgt_sm_offline(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx) +{ + stmf_change_status_t scs; + + switch (ctx->te_ctx_event) { + case TE_STMF_OFFLINE_COMPLETE_ACK: + if (tgt->target_deleting) { + tgt_sm_new_state(tgt, ctx, TS_DELETING_STMF_DEREG); + } else { + tgt_sm_new_state(tgt, ctx, TS_STMF_OFFLINE); + } + break; + case TE_DELETE: + /* TE_DELETE is handled in tgt_sm_event_dispatch() */ + break; + case TE_STMF_ONLINE_REQ: + case TE_STMF_OFFLINE_REQ: + /* + * We can't complete STMF's request since we are busy going + * offline. + */ + scs.st_completion_status = STMF_INVALID_ARG; + scs.st_additional_info = NULL; + (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ? + STMF_CMD_LPORT_ONLINE_COMPLETE : + STMF_CMD_LPORT_OFFLINE_COMPLETE, + tgt->target_stmf_lport, &scs); + break; + case TE_STMF_ONLINE_COMPLETE_ACK: + /* Ignore */ + break; + default: + ASSERT(0); + } +} + + +static void +tgt_sm_stmf_offline(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx) +{ + stmf_change_status_t scs; + + switch (ctx->te_ctx_event) { + case TE_STMF_ONLINE_REQ: + tgt_sm_new_state(tgt, ctx, TS_ONLINING); + break; + case TE_DELETE: + tgt_sm_new_state(tgt, ctx, TS_DELETING_STMF_DEREG); + break; + case TE_STMF_OFFLINE_REQ: + /* Already offline */ + scs.st_completion_status = STMF_ALREADY; + scs.st_additional_info = NULL; + (void) stmf_ctl(STMF_CMD_LPORT_OFFLINE_COMPLETE, + tgt->target_stmf_lport, &scs); + break; + case TE_STMF_ONLINE_COMPLETE_ACK: + case TE_STMF_OFFLINE_COMPLETE_ACK: + /* Ignore */ + break; + default: + ASSERT(0); + } +} + + +static void +tgt_sm_deleting_stmf_dereg(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx) +{ + stmf_change_status_t scs; + + /* Terminal state, no events */ + switch (ctx->te_ctx_event) { + case TE_STMF_ONLINE_REQ: + case TE_STMF_OFFLINE_REQ: + /* + * We can't complete STMF's request since we are being deleted + */ + scs.st_completion_status = STMF_INVALID_ARG; + scs.st_additional_info = NULL; + (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ? + STMF_CMD_LPORT_ONLINE_COMPLETE : + STMF_CMD_LPORT_OFFLINE_COMPLETE, + tgt->target_stmf_lport, &scs); + break; + case TE_STMF_ONLINE_COMPLETE_ACK: + case TE_STMF_OFFLINE_COMPLETE_ACK: + /* Ignore */ + break; + case TE_STMF_DEREG_SUCCESS: + tgt_sm_new_state(tgt, ctx, TS_DELETING); + break; + case TE_STMF_DEREG_FAIL: + tgt_sm_new_state(tgt, ctx, TS_DELETING_STMF_DEREG_FAIL); + break; + default: + ASSERT(0); + } +} + +static void +tgt_sm_deleting_stmf_dereg_fail(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx) +{ + stmf_change_status_t scs; + + /* Terminal state, no events */ + switch (ctx->te_ctx_event) { + case TE_STMF_ONLINE_REQ: + case TE_STMF_OFFLINE_REQ: + /* + * We can't complete STMF's request since we are being deleted + */ + scs.st_completion_status = STMF_INVALID_ARG; + scs.st_additional_info = NULL; + (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ? + STMF_CMD_LPORT_ONLINE_COMPLETE : + STMF_CMD_LPORT_OFFLINE_COMPLETE, + tgt->target_stmf_lport, &scs); + break; + case TE_STMF_ONLINE_COMPLETE_ACK: + case TE_STMF_OFFLINE_COMPLETE_ACK: + /* Ignore */ + break; + case TE_STMF_DEREG_RETRY: + tgt_sm_new_state(tgt, ctx, TS_DELETING_STMF_DEREG); + break; + default: + ASSERT(0); + } +} + +static void +tgt_sm_deleting(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx) +{ + stmf_change_status_t scs; + + /* Terminal state, no events */ + switch (ctx->te_ctx_event) { + case TE_STMF_ONLINE_REQ: + case TE_STMF_OFFLINE_REQ: + /* + * We can't complete STMF's request since we are being deleted + */ + scs.st_completion_status = STMF_INVALID_ARG; + scs.st_additional_info = NULL; + (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ? + STMF_CMD_LPORT_ONLINE_COMPLETE : + STMF_CMD_LPORT_OFFLINE_COMPLETE, + tgt->target_stmf_lport, &scs); + break; + case TE_STMF_ONLINE_COMPLETE_ACK: + case TE_STMF_OFFLINE_COMPLETE_ACK: + /* Ignore */ + break; + default: + ASSERT(0); + } +} + + +static void +iscsit_tgt_dereg_retry(void *arg) +{ + iscsit_tgt_t *tgt = arg; + + /* + * Rather than guaranteeing the target state machine code will not + * block for long periods of time (tying up this callout thread) + * we will queue a task on the taskq to send the retry event. + * If it fails we'll setup another timeout and try again later. + */ + if (taskq_dispatch(iscsit_global.global_dispatch_taskq, + iscsit_tgt_dereg_task, tgt, DDI_NOSLEEP) == NULL) { + /* Dispatch failed, try again later */ + (void) timeout(iscsit_tgt_dereg_retry, tgt, + drv_usectohz(TGT_DEREG_RETRY_SECONDS * 1000000)); + } +} + +static void +iscsit_tgt_dereg_task(void *arg) +{ + iscsit_tgt_t *tgt = arg; + + iscsit_tgt_sm_event(tgt, TE_STMF_DEREG_RETRY); +} + +static void +tgt_sm_new_state(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx, + iscsit_tgt_state_t new_state) +{ + stmf_local_port_t *lport = tgt->target_stmf_lport; + stmf_change_status_t scs; + stmf_state_change_info_t sci; + idm_status_t idmrc; + stmf_status_t stmfrc; + + scs.st_completion_status = STMF_SUCCESS; + scs.st_additional_info = NULL; + + /* + * Validate new state + */ + ASSERT(new_state != TS_UNDEFINED); + ASSERT3U(new_state, <, TS_MAX_STATE); + + new_state = (new_state < TS_MAX_STATE) ? + new_state : TS_UNDEFINED; + + IDM_SM_LOG(CE_NOTE, "tgt_sm_new_state: tgt %p, %s(%d) --> %s(%d)\n", + (void *) tgt, iscsit_ts_name[tgt->target_state], tgt->target_state, + iscsit_ts_name[new_state], new_state); + DTRACE_PROBE3(target__state__change, + iscsit_tgt_t *, tgt, tgt_event_ctx_t *, ctx, + iscsit_tgt_state_t, new_state); + + mutex_enter(&tgt->target_mutex); + idm_sm_audit_state_change(&tgt->target_state_audit, SAS_ISCSIT_TGT, + (int)tgt->target_state, (int)new_state); + tgt->target_last_state = tgt->target_state; + tgt->target_state = new_state; + mutex_exit(&tgt->target_mutex); + + switch (tgt->target_state) { + case TS_ONLINING: + idmrc = iscsit_tgt_online(tgt); + if (idmrc != IDM_STATUS_SUCCESS) { + scs.st_completion_status = STMF_TARGET_FAILURE; + iscsit_tgt_sm_event(tgt, TE_ONLINE_FAIL); + } else { + iscsit_tgt_sm_event(tgt, TE_ONLINE_SUCCESS); + } + /* + * Let STMF know the how the online operation completed. + * STMF will respond with an acknowlege later + */ + (void) stmf_ctl(STMF_CMD_LPORT_ONLINE_COMPLETE, lport, &scs); + break; + case TS_ONLINE: + break; + case TS_STMF_ONLINE: + (void) iscsit_isns_register(tgt); + break; + case TS_DELETING_NEED_OFFLINE: + sci.st_rflags = STMF_RFLAG_STAY_OFFLINED; + sci.st_additional_info = "Offline for delete"; + (void) stmf_ctl(STMF_CMD_LPORT_OFFLINE, lport, &sci); + break; + case TS_OFFLINING: + /* Async callback generates completion event */ + iscsit_tgt_offline(tgt); + break; + case TS_OFFLINE: + break; + case TS_STMF_OFFLINE: + break; + case TS_DELETING_STMF_DEREG: + stmfrc = stmf_deregister_local_port(tgt->target_stmf_lport); + if (stmfrc == STMF_SUCCESS) { + iscsit_tgt_sm_event(tgt, TE_STMF_DEREG_SUCCESS); + } else { + iscsit_tgt_sm_event(tgt, TE_STMF_DEREG_FAIL); + } + break; + case TS_DELETING_STMF_DEREG_FAIL: + /* Retry dereg in 1 second */ + (void) timeout(iscsit_tgt_dereg_retry, tgt, + drv_usectohz(TGT_DEREG_RETRY_SECONDS * 1000000)); + break; + case TS_DELETING: + iscsit_tgt_async_wait_ref(tgt, iscsit_tgt_unref); + break; + default: + ASSERT(0); + } +} + + +/* + * Target, TPGT, TPG utility functions + */ + +it_cfg_status_t +iscsit_config_merge_tgt(it_config_t *cfg) +{ + it_tgt_t *cfg_tgt; + iscsit_tgt_t *tgt, *next_tgt; + + + /* + * 1. >> Lock << + * 2. Removing deleted objects + * 3. Add deleted targets to global delete list + * 4. "delete" event to target state machine + * 5. >> Unlock << + * 6. Create new targets, update modified targets + */ + for (tgt = avl_first(&iscsit_global.global_target_list); + tgt != NULL; + tgt = next_tgt) { + next_tgt = AVL_NEXT(&iscsit_global.global_target_list, tgt); + + if (it_tgt_lookup(cfg, tgt->target_name) == NULL) { + avl_remove(&iscsit_global.global_target_list, tgt); + list_insert_tail( + &iscsit_global.global_deleted_target_list, tgt); + iscsit_tgt_sm_event(tgt, TE_DELETE); + } + } + + /* Now walk through the list of configured targets */ + for (cfg_tgt = cfg->config_tgt_list; + cfg_tgt != NULL; + cfg_tgt = cfg_tgt->tgt_next) { + /* See if we have an existing target */ + tgt = iscsit_tgt_lookup_locked(cfg_tgt->tgt_name); + + if (tgt == NULL) { + tgt = iscsit_tgt_create(cfg_tgt); + if (tgt == NULL) + return (ITCFG_TGT_CREATE_ERR); + avl_add(&iscsit_global.global_target_list, tgt); + } else { + (void) iscsit_tgt_modify(tgt, cfg_tgt); + iscsit_tgt_rele(tgt); + } + } + + /* + * Targets on the iscsit_global.global_deleted_target_list will remove + * and destroy themselves when their associated state machines reach + * the TS_DELETED state and all references are released. + */ + return (ITCFG_SUCCESS); +} + +iscsit_tgt_t * +iscsit_tgt_lookup(char *target_name) +{ + iscsit_tgt_t *result; + + ISCSIT_GLOBAL_LOCK(RW_READER); + result = iscsit_tgt_lookup_locked(target_name); + ISCSIT_GLOBAL_UNLOCK(); + + return (result); +} + +iscsit_tgt_t * +iscsit_tgt_lookup_locked(char *target_name) +{ + iscsit_tgt_t tmp_tgt; + iscsit_tgt_t *result; + + /* + * Use a dummy target for lookup, filling in all fields used in AVL + * comparison. + */ + tmp_tgt.target_name = target_name; + if ((result = avl_find(&iscsit_global.global_target_list, + &tmp_tgt, NULL)) != NULL) { + iscsit_tgt_hold(result); + } + + return (result); +} + +iscsit_tgt_t * +iscsit_tgt_create(it_tgt_t *cfg_tgt) +{ + iscsit_tgt_t *result; + stmf_local_port_t *lport; + + /* + * Each target is an STMF local port. + */ + lport = stmf_alloc(STMF_STRUCT_STMF_LOCAL_PORT, + sizeof (iscsit_tgt_t) + sizeof (scsi_devid_desc_t) + + strnlen(cfg_tgt->tgt_name, MAX_ISCSI_NODENAMELEN) + 1, 0); + if (lport == NULL) { + return (NULL); + } + + result = lport->lport_port_private; + result->target_state = TS_CREATED; + result->target_stmf_lport_registered = 0; + /* Use pointer arithmetic to find scsi_devid_desc_t */ + result->target_devid = (scsi_devid_desc_t *)(result + 1); + (void) strcpy((char *)result->target_devid->ident, cfg_tgt->tgt_name); + result->target_devid->ident_length = + strnlen(cfg_tgt->tgt_name, MAX_ISCSI_NODENAMELEN); + result->target_devid->protocol_id = PROTOCOL_iSCSI; + result->target_devid->piv = 1; + result->target_devid->code_set = CODE_SET_ASCII; + result->target_devid->association = ID_IS_TARGET_PORT; + + /* Store a shortcut to the target name */ + result->target_name = (char *)result->target_devid->ident; + idm_sm_audit_init(&result->target_state_audit); + mutex_init(&result->target_mutex, NULL, MUTEX_DEFAULT, NULL); + avl_create(&result->target_sess_list, iscsit_sess_avl_compare, + sizeof (iscsit_sess_t), offsetof(iscsit_sess_t, ist_tgt_ln)); + avl_create(&result->target_tpgt_list, iscsit_tpgt_avl_compare, + sizeof (iscsit_tpgt_t), offsetof(iscsit_tpgt_t, tpgt_tgt_ln)); + list_create(&result->target_events, sizeof (tgt_event_ctx_t), + offsetof(tgt_event_ctx_t, te_ctx_node)); + idm_refcnt_init(&result->target_refcnt, result); + idm_refcnt_init(&result->target_sess_refcnt, result); + + /* Finish initializing local port */ + lport->lport_abort_timeout = 0xffffffff; /* seconds */ + lport->lport_id = result->target_devid; + lport->lport_pp = iscsit_global.global_pp; + lport->lport_ds = iscsit_global.global_dbuf_store; + lport->lport_xfer_data = &iscsit_xfer_scsi_data; + lport->lport_send_status = &iscsit_send_scsi_status; + lport->lport_task_free = &iscsit_lport_task_free; + lport->lport_abort = &iscsit_abort; + lport->lport_ctl = &iscsit_ctl; + result->target_stmf_lport = lport; + + /* + * Additional target modifications from config + */ + if (iscsit_tgt_modify(result, cfg_tgt) != IDM_STATUS_SUCCESS) { + iscsit_tgt_destroy(result); + return (NULL); + } + + /* + * Register the target with STMF but not until we have all the + * TPGT bindings and any other additional config setup. STMF + * may immediately ask us to go online. + */ + if (stmf_register_local_port(lport) != STMF_SUCCESS) { + iscsit_tgt_destroy(result); + return (NULL); + } + result->target_stmf_lport_registered = 1; + + iscsit_global_hold(); + + return (result); +} + +static idm_status_t +iscsit_tgt_modify(iscsit_tgt_t *tgt, it_tgt_t *cfg_tgt) +{ + idm_status_t idmrc = IDM_STATUS_SUCCESS; + list_t tpgt_del_list; + + /* Merge TPGT */ + list_create(&tpgt_del_list, sizeof (iscsit_tpgt_t), + offsetof(iscsit_tpgt_t, tpgt_delete_ln)); + + mutex_enter(&tgt->target_mutex); + if (tgt->target_props) { + nvlist_free(tgt->target_props); + tgt->target_props = NULL; + } + (void) nvlist_dup(cfg_tgt->tgt_properties, &tgt->target_props, + KM_SLEEP); + + if ((idmrc = iscsit_tgt_merge_tpgt(tgt, cfg_tgt, &tpgt_del_list)) != + IDM_STATUS_SUCCESS) { + /* This should never happen */ + cmn_err(CE_WARN, "Fail to configure TPGTs for " + "target %s, the target modification could not be " + "completed.", tgt->target_name); + } + + mutex_exit(&tgt->target_mutex); + + iscsit_config_destroy_tpgts(&tpgt_del_list); + + /* + * If the target is truly modified (not newly created), + * inform iSNS to update the target registration. + */ + if ((tgt->target_generation > 0) && + (cfg_tgt->tgt_generation > tgt->target_generation)) { + iscsit_isns_target_update(tgt); + } + + tgt->target_generation = cfg_tgt->tgt_generation; + + return (idmrc); +} + +void +iscsit_config_destroy_tpgts(list_t *tpgt_del_list) +{ + iscsit_tpgt_t *tpgt, *next_tpgt; + + for (tpgt = list_head(tpgt_del_list); + tpgt != NULL; + tpgt = next_tpgt) { + next_tpgt = list_next(tpgt_del_list, tpgt); + + list_remove(tpgt_del_list, tpgt); + idm_refcnt_wait_ref(&tpgt->tpgt_refcnt); + iscsit_tpgt_destroy(tpgt); + } +} + +void +iscsit_tgt_unref(void *tgt_void) +{ + iscsit_tgt_t *tgt = tgt_void; + + ISCSIT_GLOBAL_LOCK(RW_WRITER); + list_remove(&iscsit_global.global_deleted_target_list, tgt); + ISCSIT_GLOBAL_UNLOCK(); + iscsit_tgt_destroy(tgt); +} + +void +iscsit_tgt_async_wait_ref(iscsit_tgt_t *tgt, idm_refcnt_cb_t *cb_func) +{ + idm_refcnt_async_wait_ref(&tgt->target_refcnt, cb_func); +} + +static void +iscsit_tgt_destroy(iscsit_tgt_t *tgt) +{ + iscsit_tpgt_t *tpgt, *next_tpgt; + + ASSERT(tgt->target_state == TS_DELETING); + + /* + * Destroy all target portal group tags + */ + mutex_enter(&tgt->target_mutex); + for (tpgt = avl_first(&tgt->target_tpgt_list); + tpgt != NULL; + tpgt = next_tpgt) { + next_tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt); + avl_remove(&tgt->target_tpgt_list, tpgt); + iscsit_tpgt_destroy(tpgt); + } + + if (tgt->target_props) { + nvlist_free(tgt->target_props); + } + mutex_exit(&tgt->target_mutex); + + /* + * Destroy target + */ + idm_refcnt_destroy(&tgt->target_sess_refcnt); + idm_refcnt_destroy(&tgt->target_refcnt); + list_destroy(&tgt->target_events); + avl_destroy(&tgt->target_tpgt_list); + avl_destroy(&tgt->target_sess_list); + mutex_destroy(&tgt->target_mutex); + stmf_free(tgt->target_stmf_lport); /* Also frees "tgt' */ + iscsit_global_rele(); +} + +void +iscsit_tgt_hold(iscsit_tgt_t *tgt) +{ + idm_refcnt_hold(&tgt->target_refcnt); +} + +void +iscsit_tgt_rele(iscsit_tgt_t *tgt) +{ + idm_refcnt_rele(&tgt->target_refcnt); +} + +int +iscsit_tgt_avl_compare(const void *void_tgt1, const void *void_tgt2) +{ + const iscsit_tgt_t *tgt1 = void_tgt1; + const iscsit_tgt_t *tgt2 = void_tgt2; + int result; + + /* + * Sort by ISID first then TSIH + */ + result = strcmp(tgt1->target_name, tgt2->target_name); + if (result < 0) { + return (-1); + } else if (result > 0) { + return (1); + } + + return (0); +} + + +iscsit_tpgt_t * +iscsit_tgt_lookup_tpgt(iscsit_tgt_t *tgt, uint16_t tag) +{ + iscsit_tpgt_t *result; + + mutex_enter(&tgt->target_mutex); + result = iscsit_tgt_lookup_tpgt_locked(tgt, tag); + mutex_exit(&tgt->target_mutex); + + return (result); +} + +static iscsit_tpgt_t * +iscsit_tgt_lookup_tpgt_locked(iscsit_tgt_t *tgt, uint16_t tag) +{ + iscsit_tpgt_t tmp_tpgt; + iscsit_tpgt_t *result; + + /* Caller holds tgt->target_mutex */ + tmp_tpgt.tpgt_tag = tag; + if ((result = avl_find(&tgt->target_tpgt_list, &tmp_tpgt, NULL)) != + NULL) { + iscsit_tpgt_hold(result); + } + + return (result); +} + +iscsit_portal_t * +iscsit_tgt_lookup_portal(iscsit_tgt_t *tgt, struct sockaddr_storage *sa, + iscsit_tpgt_t **output_tpgt) +{ + iscsit_tpgt_t *tpgt; + iscsit_portal_t *portal; + + /* Caller holds tgt->target_mutex */ + ASSERT(mutex_owned(&tgt->target_mutex)); + for (tpgt = avl_first(&tgt->target_tpgt_list); + tpgt != NULL; + tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) { + portal = iscsit_tpg_portal_lookup(tpgt->tpgt_tpg, sa); + if (portal) { + iscsit_tpgt_hold(tpgt); + *output_tpgt = tpgt; + return (portal); + } + } + + return (NULL); +} + + +void +iscsit_tgt_bind_sess(iscsit_tgt_t *tgt, iscsit_sess_t *sess) +{ + if (tgt) { + sess->ist_lport = tgt->target_stmf_lport; + iscsit_tgt_hold(tgt); + idm_refcnt_hold(&tgt->target_sess_refcnt); + mutex_enter(&tgt->target_mutex); + avl_add(&tgt->target_sess_list, sess); + mutex_exit(&tgt->target_mutex); + } else { + /* Discovery session */ + sess->ist_lport = NULL; + iscsit_global_hold(); + ISCSIT_GLOBAL_LOCK(RW_WRITER); + avl_add(&iscsit_global.global_discovery_sessions, sess); + ISCSIT_GLOBAL_UNLOCK(); + } +} + +void +iscsit_tgt_unbind_sess(iscsit_tgt_t *tgt, iscsit_sess_t *sess) +{ + if (tgt) { + mutex_enter(&tgt->target_mutex); + avl_remove(&tgt->target_sess_list, sess); + mutex_exit(&tgt->target_mutex); + sess->ist_tgt = (iscsit_tgt_t *)SESS_UNBOUND_FROM_TGT; + idm_refcnt_rele(&tgt->target_sess_refcnt); + iscsit_tgt_rele(tgt); + } else { + /* Discovery session */ + ISCSIT_GLOBAL_LOCK(RW_WRITER); + avl_remove(&iscsit_global.global_discovery_sessions, sess); + ISCSIT_GLOBAL_UNLOCK(); + iscsit_global_rele(); + } +} + +#define LOCK_FOR_SESS_LOOKUP(lookup_tgt) { \ + if ((lookup_tgt) == NULL) { \ + ISCSIT_GLOBAL_LOCK(RW_READER); \ + } else { \ + mutex_enter(&(lookup_tgt)->target_mutex); \ + } \ +} + +#define UNLOCK_FOR_SESS_LOOKUP(lookup_tgt) { \ + if ((lookup_tgt) == NULL) { \ + ISCSIT_GLOBAL_UNLOCK(); \ + } else { \ + mutex_exit(&(lookup_tgt)->target_mutex); \ + } \ +} + +iscsit_sess_t * +iscsit_tgt_lookup_sess(iscsit_tgt_t *tgt, char *initiator_name, + uint8_t *isid, uint16_t tsih, uint16_t tag) +{ + iscsit_sess_t tmp_sess; + avl_tree_t *sess_avl; + avl_index_t where; + iscsit_sess_t *result; + + /* + * If tgt is NULL then we are looking for a discovery session + */ + if (tgt == NULL) { + sess_avl = &iscsit_global.global_discovery_sessions; + } else { + sess_avl = &tgt->target_sess_list; + } + + LOCK_FOR_SESS_LOOKUP(tgt); + if (avl_numnodes(sess_avl) == NULL) { + UNLOCK_FOR_SESS_LOOKUP(tgt); + return (NULL); + } + + /* + * We'll try to find a session matching ISID + TSIH first. If we + * can't find one then we will return the closest match. If the + * caller needs an exact match it must compare the TSIH after + * the session is returned. + * + * The reason we do this "fuzzy matching" is to allow matching + * sessions with different TSIH values on the same AVL list. This + * makes session reinstatement much easier since the new session can + * live on the list at the same time as the old session is cleaning up. + */ + bcopy(isid, tmp_sess.ist_isid, ISCSI_ISID_LEN); + tmp_sess.ist_initiator_name = initiator_name; + tmp_sess.ist_tsih = tsih; + tmp_sess.ist_tpgt_tag = tag; + + result = avl_find(sess_avl, &tmp_sess, &where); + if (result != NULL) { + iscsit_sess_hold(result); + UNLOCK_FOR_SESS_LOOKUP(tgt); + return (result); + } + + /* + * avl_find_nearest() may return a result with a different ISID so + * we should only return a result if the name and ISID match + */ + result = avl_nearest(sess_avl, where, AVL_BEFORE); + if ((result != NULL) && + (strcmp(result->ist_initiator_name, initiator_name) == 0) && + (memcmp(result->ist_isid, isid, ISCSI_ISID_LEN) == 0) && + (result->ist_tpgt_tag == tag)) { + iscsit_sess_hold(result); + UNLOCK_FOR_SESS_LOOKUP(tgt); + return (result); + } + + result = avl_nearest(sess_avl, where, AVL_AFTER); + if ((result != NULL) && + (strcmp(result->ist_initiator_name, initiator_name) == 0) && + (memcmp(result->ist_isid, isid, ISCSI_ISID_LEN) == 0) && + (result->ist_tpgt_tag == tag)) { + iscsit_sess_hold(result); + UNLOCK_FOR_SESS_LOOKUP(tgt); + return (result); + } + + UNLOCK_FOR_SESS_LOOKUP(tgt); + + return (NULL); +} + +static idm_status_t +iscsit_tgt_merge_tpgt(iscsit_tgt_t *tgt, it_tgt_t *cfg_tgt, + list_t *tpgt_del_list) +{ + iscsit_tpgt_t *tpgt, *next_tpgt; + it_tpgt_t *cfg_tpgt; + + /* + * 1. >> Lock << + * 2. Removing all objects and place on a temp list + * 3. Add new objects + * 4. >> Unlock << + * 5. tpgt_del_list contains deleted objects + */ + ASSERT(avl_is_empty(&tgt->target_tpgt_list) || + (tpgt_del_list != NULL)); + + if (tpgt_del_list) { + for (tpgt = avl_first(&tgt->target_tpgt_list); + tpgt != NULL; tpgt = next_tpgt) { + next_tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt); + avl_remove(&tgt->target_tpgt_list, tpgt); + if (tgt->target_state == TS_STMF_ONLINE) { + tpgt->tpgt_needs_tpg_offline = B_TRUE; + } + list_insert_tail(tpgt_del_list, tpgt); + } + } + + if (cfg_tgt->tgt_tpgt_list == NULL) { + tpgt = iscsit_tpgt_create_default(); + if (tgt->target_state == TS_STMF_ONLINE) { + (void) iscsit_tpg_online(tpgt->tpgt_tpg); + } + avl_add(&tgt->target_tpgt_list, tpgt); + } else { + /* Add currently defined TPGTs */ + for (cfg_tpgt = cfg_tgt->tgt_tpgt_list; + cfg_tpgt != NULL; + cfg_tpgt = cfg_tpgt->tpgt_next) { + tpgt = iscsit_tpgt_create(cfg_tpgt); + if (tpgt == NULL) + goto tpgt_merge_error; + if (tgt->target_state == TS_STMF_ONLINE) { + (void) iscsit_tpg_online(tpgt->tpgt_tpg); + } + avl_add(&tgt->target_tpgt_list, tpgt); + } + } + + return (IDM_STATUS_SUCCESS); + +tpgt_merge_error: + /* + * This is a problem in configuration we received so it should never + * happen. That's good because we're probably no longer in a state + * requested by the user -- all the unbind operations have already + * taken place. + */ + return (IDM_STATUS_FAIL); +} + +static iscsit_tpgt_t * +iscsit_tpgt_create(it_tpgt_t *cfg_tpgt) +{ + iscsit_tpg_t *tpg; + iscsit_tpgt_t *result; + + /* This takes a reference on the TPG */ + tpg = iscsit_tpg_lookup_locked(cfg_tpgt->tpgt_tpg_name); + if (tpg == NULL) + return (NULL); + + result = kmem_zalloc(sizeof (*result), KM_SLEEP); + + result->tpgt_tpg = tpg; + result->tpgt_tag = cfg_tpgt->tpgt_tag; + + return (result); +} + +iscsit_tpgt_t * +iscsit_tpgt_create_default() +{ + iscsit_tpgt_t *result; + + result = kmem_zalloc(sizeof (*result), KM_SLEEP); + + result->tpgt_tpg = iscsit_global.global_default_tpg; + iscsit_tpg_hold(result->tpgt_tpg); + result->tpgt_tag = ISCSIT_DEFAULT_TPGT; + + return (result); +} + +void +iscsit_tpgt_destroy(iscsit_tpgt_t *tpgt) +{ + if (tpgt->tpgt_needs_tpg_offline) { + iscsit_tpg_offline(tpgt->tpgt_tpg); + } + iscsit_tpg_rele(tpgt->tpgt_tpg); + kmem_free(tpgt, sizeof (*tpgt)); +} + +void +iscsit_tpgt_hold(iscsit_tpgt_t *tpgt) +{ + idm_refcnt_hold(&tpgt->tpgt_refcnt); +} + +void +iscsit_tpgt_rele(iscsit_tpgt_t *tpgt) +{ + idm_refcnt_rele(&tpgt->tpgt_refcnt); +} + +int +iscsit_tpgt_avl_compare(const void *void_tpgt1, const void *void_tpgt2) +{ + const iscsit_tpgt_t *tpgt1 = void_tpgt1; + const iscsit_tpgt_t *tpgt2 = void_tpgt2; + + if (tpgt1->tpgt_tag < tpgt2->tpgt_tag) + return (-1); + else if (tpgt1->tpgt_tag > tpgt2->tpgt_tag) + return (1); + + return (0); +} + +static idm_status_t +iscsit_tgt_online(iscsit_tgt_t *tgt) +{ + iscsit_tpgt_t *tpgt, *tpgt_fail; + idm_status_t rc; + + mutex_enter(&tgt->target_mutex); + + ASSERT(tgt->target_sess_list.avl_numnodes == 0); + idm_refcnt_reset(&tgt->target_sess_refcnt); + for (tpgt = avl_first(&tgt->target_tpgt_list); + tpgt != NULL; + tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) { + rc = iscsit_tpg_online(tpgt->tpgt_tpg); + if (rc != IDM_STATUS_SUCCESS) { + tpgt_fail = tpgt; + goto tgt_online_fail; + } + } + + mutex_exit(&tgt->target_mutex); + + return (IDM_STATUS_SUCCESS); + +tgt_online_fail: + /* Offline all the tpgs we successfully onlined up to the failure */ + for (tpgt = avl_first(&tgt->target_tpgt_list); + tpgt != tpgt_fail; + tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) { + iscsit_tpg_offline(tpgt->tpgt_tpg); + } + mutex_exit(&tgt->target_mutex); + return (rc); +} + +static void +iscsit_tgt_offline_cb(void *tgt_void) +{ + iscsit_tgt_t *tgt = tgt_void; + stmf_change_status_t scs; + + iscsit_tgt_sm_event(tgt, TE_OFFLINE_COMPLETE); + + scs.st_completion_status = STMF_SUCCESS; + scs.st_additional_info = NULL; + (void) stmf_ctl(STMF_CMD_LPORT_OFFLINE_COMPLETE, + tgt->target_stmf_lport, &scs); +} + +static void +iscsit_tgt_offline(iscsit_tgt_t *tgt) +{ + iscsit_tpgt_t *tpgt; + iscsit_sess_t *ist; + + mutex_enter(&tgt->target_mutex); + + /* Offline target portal groups */ + for (tpgt = avl_first(&tgt->target_tpgt_list); + tpgt != NULL; + tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) { + iscsit_tpg_offline(tpgt->tpgt_tpg); + } + + /* Close any active sessions */ + for (ist = avl_first(&tgt->target_sess_list); + ist != NULL; + ist = AVL_NEXT(&tgt->target_sess_list, ist)) { + /* + * This is not a synchronous operation but after all + * sessions have been cleaned up there will be no + * more session-related holds on the target. + */ + iscsit_sess_close(ist); + } + + mutex_exit(&tgt->target_mutex); + + /* + * Wait for all the sessions to quiesce. + */ + idm_refcnt_async_wait_ref(&tgt->target_sess_refcnt, + &iscsit_tgt_offline_cb); +} + +it_cfg_status_t +iscsit_config_merge_tpg(it_config_t *cfg, list_t *tpg_del_list) +{ + it_tpg_t *cfg_tpg; + iscsit_tpg_t *tpg, *next_tpg; + + /* + * 1. >> Lock << + * 2. Removing deleted objects and place on a temp list + * 3. Add new objects + * 4. >> Unlock << + * 5. tpg_del_list contains objects to destroy + */ + for (tpg = avl_first(&iscsit_global.global_tpg_list); + tpg != NULL; + tpg = next_tpg) { + next_tpg = AVL_NEXT(&iscsit_global.global_tpg_list, tpg); + + if (it_tpg_lookup(cfg, tpg->tpg_name) == NULL) { + /* + * The policy around when to allow a target portal + * group to be deleted is implemented in libiscsit. + * By the time the request gets to the kernel module + * we expect that it conforms to policy so we will + * cleanup all references to TPG and destroy it if it + * is possible to do so. + * + */ + avl_remove(&iscsit_global.global_tpg_list, tpg); + list_insert_tail(tpg_del_list, tpg); + } + } + + /* Now walk through the list of configured target portal groups */ + for (cfg_tpg = cfg->config_tpg_list; + cfg_tpg != NULL; + cfg_tpg = cfg_tpg->tpg_next) { + /* See if we have an existing target portal group */ + tpg = iscsit_tpg_lookup_locked(cfg_tpg->tpg_name); + + if (tpg == NULL) { + tpg = iscsit_tpg_create(cfg_tpg); + ASSERT(tpg != NULL); + avl_add(&iscsit_global.global_tpg_list, tpg); + } else { + mutex_enter(&tpg->tpg_mutex); + iscsit_tpg_modify(tpg, cfg_tpg); + mutex_exit(&tpg->tpg_mutex); + iscsit_tpg_rele(tpg); + } + } + + return (ITCFG_SUCCESS); +} + + +void +iscsit_config_destroy_tpgs(list_t *tpg_del_list) +{ + iscsit_tpg_t *tpg, *next_tpg; + + /* Now finish destroying the target portal groups */ + for (tpg = list_head(tpg_del_list); + tpg != NULL; + tpg = next_tpg) { + next_tpg = list_next(tpg_del_list, tpg); + list_remove(tpg_del_list, tpg); + idm_refcnt_wait_ref(&tpg->tpg_refcnt); + + /* Kill it */ + iscsit_tpg_destroy(tpg); + } +} + +iscsit_tpg_t * +iscsit_tpg_lookup(char *tpg_name) +{ + iscsit_tpg_t *result; + + ISCSIT_GLOBAL_LOCK(RW_READER); + result = iscsit_tpg_lookup_locked(tpg_name); + ISCSIT_GLOBAL_UNLOCK(); + + return (result); +} + +static iscsit_tpg_t * +iscsit_tpg_lookup_locked(char *tpg_name) +{ + iscsit_tpg_t tmp_tpg; + iscsit_tpg_t *result; + + (void) strlcpy(tmp_tpg.tpg_name, tpg_name, MAX_ISCSI_NODENAMELEN); + if ((result = avl_find(&iscsit_global.global_tpg_list, + &tmp_tpg, NULL)) != NULL) { + iscsit_tpg_hold(result); + } + + return (result); +} + +iscsit_tpg_t * +iscsit_tpg_create(it_tpg_t *cfg_tpg) +{ + iscsit_tpg_t *tpg; + + tpg = kmem_zalloc(sizeof (*tpg), KM_SLEEP); + + mutex_init(&tpg->tpg_mutex, NULL, MUTEX_DEFAULT, NULL); + (void) strlcpy(tpg->tpg_name, cfg_tpg->tpg_name, MAX_TPG_NAMELEN); + avl_create(&tpg->tpg_portal_list, iscsit_portal_avl_compare, + sizeof (iscsit_portal_t), offsetof(iscsit_portal_t, portal_tpg_ln)); + idm_refcnt_init(&tpg->tpg_refcnt, tpg); + + mutex_enter(&tpg->tpg_mutex); + iscsit_tpg_modify(tpg, cfg_tpg); + mutex_exit(&tpg->tpg_mutex); + iscsit_global_hold(); + + return (tpg); +} + +static void +iscsit_tpg_modify(iscsit_tpg_t *tpg, it_tpg_t *cfg_tpg) +{ + iscsit_portal_t *portal, *next_portal; + it_portal_t *cfg_portal; + + /* Update portals */ + for (portal = avl_first(&tpg->tpg_portal_list); + portal != NULL; + portal = next_portal) { + next_portal = AVL_NEXT(&tpg->tpg_portal_list, portal); + if (it_portal_lookup(cfg_tpg, &portal->portal_addr) == NULL) { + avl_remove(&tpg->tpg_portal_list, portal); + iscsit_portal_destroy(portal); + } + } + + for (cfg_portal = cfg_tpg->tpg_portal_list; + cfg_portal != NULL; + cfg_portal = cfg_portal->next) { + if ((portal = iscsit_tpg_portal_lookup_locked(tpg, + &cfg_portal->portal_addr)) == NULL) { + (void) iscsit_portal_create(tpg, + &cfg_portal->portal_addr); + } else { + iscsit_portal_rele(portal); + } + } +} + +void +iscsit_tpg_destroy(iscsit_tpg_t *tpg) +{ + iscsit_portal_t *portal, *next_portal; + + for (portal = avl_first(&tpg->tpg_portal_list); + portal != NULL; + portal = next_portal) { + next_portal = AVL_NEXT(&tpg->tpg_portal_list, portal); + avl_remove(&tpg->tpg_portal_list, portal); + iscsit_portal_destroy(portal); + } + + idm_refcnt_wait_ref(&tpg->tpg_refcnt); + idm_refcnt_destroy(&tpg->tpg_refcnt); + avl_destroy(&tpg->tpg_portal_list); + mutex_destroy(&tpg->tpg_mutex); + kmem_free(tpg, sizeof (*tpg)); + iscsit_global_rele(); +} + +void +iscsit_tpg_hold(iscsit_tpg_t *tpg) +{ + idm_refcnt_hold(&tpg->tpg_refcnt); +} + +void +iscsit_tpg_rele(iscsit_tpg_t *tpg) +{ + idm_refcnt_rele(&tpg->tpg_refcnt); +} + +iscsit_tpg_t * +iscsit_tpg_createdefault() +{ + iscsit_tpg_t *tpg; + + tpg = kmem_zalloc(sizeof (*tpg), KM_SLEEP); + + mutex_init(&tpg->tpg_mutex, NULL, MUTEX_DEFAULT, NULL); + (void) strlcpy(tpg->tpg_name, ISCSIT_DEFAULT_TPG, MAX_TPG_NAMELEN); + avl_create(&tpg->tpg_portal_list, iscsit_portal_avl_compare, + sizeof (iscsit_portal_t), offsetof(iscsit_portal_t, portal_tpg_ln)); + idm_refcnt_init(&tpg->tpg_refcnt, tpg); + + /* Now create default portal */ + if (iscsit_portal_create(tpg, NULL) == NULL) { + iscsit_tpg_destroy(tpg); + return (NULL); + } + + return (tpg); +} + +void +iscsit_tpg_destroydefault(iscsit_tpg_t *tpg) +{ + iscsit_portal_t *portal; + + portal = avl_first(&tpg->tpg_portal_list); + ASSERT(portal != NULL); + avl_remove(&tpg->tpg_portal_list, portal); + iscsit_portal_destroy(portal); + + idm_refcnt_wait_ref(&tpg->tpg_refcnt); + idm_refcnt_destroy(&tpg->tpg_refcnt); + avl_destroy(&tpg->tpg_portal_list); + mutex_destroy(&tpg->tpg_mutex); + kmem_free(tpg, sizeof (*tpg)); +} + +int +iscsit_tpg_avl_compare(const void *void_tpg1, const void *void_tpg2) +{ + const iscsit_tpg_t *tpg1 = void_tpg1; + const iscsit_tpg_t *tpg2 = void_tpg2; + int result; + + /* + * Sort by ISID first then TSIH + */ + result = strcmp(tpg1->tpg_name, tpg2->tpg_name); + if (result < 0) { + return (-1); + } else if (result > 0) { + return (1); + } + + return (0); +} + +idm_status_t +iscsit_tpg_online(iscsit_tpg_t *tpg) +{ + iscsit_portal_t *portal, *portal_fail; + idm_status_t rc; + + mutex_enter(&tpg->tpg_mutex); + if (tpg->tpg_online == 0) { + for (portal = avl_first(&tpg->tpg_portal_list); + portal != NULL; + portal = AVL_NEXT(&tpg->tpg_portal_list, portal)) { + rc = iscsit_portal_online(portal); + if (rc != IDM_STATUS_SUCCESS) { + portal_fail = portal; + goto tpg_online_fail; + } + } + } + tpg->tpg_online++; + + mutex_exit(&tpg->tpg_mutex); + return (IDM_STATUS_SUCCESS); + +tpg_online_fail: + /* Offline all the portals we successfully onlined up to the failure */ + for (portal = avl_first(&tpg->tpg_portal_list); + portal != portal_fail; + portal = AVL_NEXT(&tpg->tpg_portal_list, portal)) { + iscsit_portal_offline(portal); + } + mutex_exit(&tpg->tpg_mutex); + return (rc); +} + +void +iscsit_tpg_offline(iscsit_tpg_t *tpg) +{ + iscsit_portal_t *portal; + + mutex_enter(&tpg->tpg_mutex); + tpg->tpg_online--; + if (tpg->tpg_online == 0) { + for (portal = avl_first(&tpg->tpg_portal_list); + portal != NULL; + portal = AVL_NEXT(&tpg->tpg_portal_list, portal)) { + iscsit_portal_offline(portal); + } + } + mutex_exit(&tpg->tpg_mutex); +} + +iscsit_portal_t * +iscsit_tpg_portal_lookup(iscsit_tpg_t *tpg, struct sockaddr_storage *sa) +{ + iscsit_portal_t *result; + + mutex_enter(&tpg->tpg_mutex); + result = iscsit_tpg_portal_lookup_locked(tpg, sa); + mutex_exit(&tpg->tpg_mutex); + + return (result); +} + +static iscsit_portal_t * +iscsit_tpg_portal_lookup_locked(iscsit_tpg_t *tpg, + struct sockaddr_storage *sa) +{ + iscsit_portal_t tmp_portal; + iscsit_portal_t *result; + + /* Caller holds tpg->tpg_mutex */ + bcopy(sa, &tmp_portal.portal_addr, sizeof (*sa)); + if ((result = avl_find(&tpg->tpg_portal_list, &tmp_portal, NULL)) != + NULL) { + iscsit_portal_hold(result); + } + + return (result); +} + +iscsit_portal_t * +iscsit_portal_create(iscsit_tpg_t *tpg, struct sockaddr_storage *sa) +{ + iscsit_portal_t *portal; + + portal = kmem_zalloc(sizeof (*portal), KM_SLEEP); + /* + * If (sa == NULL) then we are being asked to create the default + * portal -- targets will use this portal when no portals are + * explicitly configured. + */ + if (sa != NULL) { + bcopy(sa, &portal->portal_addr, sizeof (*sa)); + } + + idm_refcnt_init(&portal->portal_refcnt, portal); + + /* + * Add this portal to the list + */ + avl_add(&tpg->tpg_portal_list, portal); + + return (portal); +} + +void +iscsit_portal_destroy(iscsit_portal_t *portal) +{ + ASSERT(portal->portal_svc == NULL); + idm_refcnt_destroy(&portal->portal_refcnt); + kmem_free(portal, sizeof (*portal)); +} + +void +iscsit_portal_hold(iscsit_portal_t *portal) +{ + idm_refcnt_hold(&portal->portal_refcnt); +} + +void +iscsit_portal_rele(iscsit_portal_t *portal) +{ + idm_refcnt_rele(&portal->portal_refcnt); +} + +int +iscsit_portal_avl_compare(const void *void_portal1, const void *void_portal2) +{ + const iscsit_portal_t *portal1 = void_portal1; + const iscsit_portal_t *portal2 = void_portal2; + const struct sockaddr_storage *ss1, *ss2; + const struct in_addr *in1, *in2; + const struct in6_addr *in61, *in62; + int i; + + /* + * Compare ports, then address family, then ip address + */ + ss1 = &portal1->portal_addr; + ss2 = &portal2->portal_addr; + if (((struct sockaddr_in *)ss1)->sin_port != + ((struct sockaddr_in *)ss2)->sin_port) { + if (((struct sockaddr_in *)ss1)->sin_port > + ((struct sockaddr_in *)ss2)->sin_port) + return (1); + else + return (-1); + } + + /* + * ports are the same + */ + if (ss1->ss_family != ss2->ss_family) { + if (ss1->ss_family == AF_INET) + return (1); + else + return (-1); + } + /* + * address families are the same + */ + if (ss1->ss_family == AF_INET) { + in1 = &((struct sockaddr_in *)ss1)->sin_addr; + in2 = &((struct sockaddr_in *)ss2)->sin_addr; + + if (in1->s_addr > in2->s_addr) + return (1); + else if (in1->s_addr < in2->s_addr) + return (-1); + else + return (0); + } else if (ss1->ss_family == AF_INET6) { + in61 = &((struct sockaddr_in6 *)ss1)->sin6_addr; + in62 = &((struct sockaddr_in6 *)ss2)->sin6_addr; + + for (i = 0; i < 4; i++) { + if (in61->s6_addr32[i] > in62->s6_addr32[i]) + return (1); + else if (in61->s6_addr32[i] < in62->s6_addr32[i]) + return (-1); + } + return (0); + } else + cmn_err(CE_WARN, + "iscsit_portal_avl_compare: unknown ss_family %d", + ss1->ss_family); + + return (1); +} + + +idm_status_t +iscsit_portal_online(iscsit_portal_t *portal) +{ + idm_status_t rc; + idm_svc_t *svc; + idm_svc_req_t sr; + uint16_t port; + struct sockaddr_in *sin; + + /* Caller holds parent TPG mutex */ + if (portal->portal_online == 0) { + /* + * If there is no existing IDM service instance for this port, + * create one. If the service exists, then the lookup, + * creates a reference on the existing service. + */ + sin = (struct sockaddr_in *)&portal->portal_addr; + port = ntohs(sin->sin_port); + if (port == 0) + port = ISCSI_LISTEN_PORT; + ASSERT(portal->portal_svc == NULL); + if ((svc = idm_tgt_svc_lookup(port)) == NULL) { + sr.sr_port = port; + sr.sr_li = iscsit_global.global_li; + sr.sr_conn_ops.icb_rx_scsi_cmd = &iscsit_op_scsi_cmd; + sr.sr_conn_ops.icb_rx_scsi_rsp = NULL; + sr.sr_conn_ops.icb_rx_misc = &iscsit_rx_pdu; + sr.sr_conn_ops.icb_rx_error = &iscsit_rx_pdu_error; + sr.sr_conn_ops.icb_task_aborted = &iscsit_task_aborted; + sr.sr_conn_ops.icb_client_notify = + &iscsit_client_notify; + sr.sr_conn_ops.icb_build_hdr = &iscsit_build_hdr; + + if (idm_tgt_svc_create(&sr, &svc) != + IDM_STATUS_SUCCESS) { + return (IDM_STATUS_FAIL); + } + + /* Get reference on the service we just created */ + idm_tgt_svc_hold(svc); + } + if ((rc = idm_tgt_svc_online(svc)) != IDM_STATUS_SUCCESS) { + idm_tgt_svc_rele_and_destroy(svc); + return (IDM_STATUS_FAIL); + } + portal->portal_svc = svc; + + /* + * Only call iSNS for first online + */ + iscsit_isns_portal_online(portal); + } + + portal->portal_online++; + + return (rc); +} + +void +iscsit_portal_offline(iscsit_portal_t *portal) +{ + portal->portal_online--; + + if (portal->portal_online == 0) { + /* + * Only call iSNS for last offline + */ + iscsit_isns_portal_offline(portal); + idm_tgt_svc_offline(portal->portal_svc); + /* If service is unreferenced, destroy it too */ + idm_tgt_svc_rele_and_destroy(portal->portal_svc); + portal->portal_svc = NULL; + } + +} + +it_cfg_status_t +iscsit_config_merge_ini(it_config_t *cfg) +{ + iscsit_ini_t *ini, *next_ini; + it_ini_t *cfg_ini; + + /* + * Initiator objects are so simple we will just destroy all the current + * objects and build new ones. Nothing should ever reference an + * initator object.. instead just lookup the initiator object and + * grab the properties while holding the global config lock. + */ + for (ini = avl_first(&iscsit_global.global_ini_list); + ini != NULL; + ini = next_ini) { + next_ini = AVL_NEXT(&iscsit_global.global_ini_list, ini); + avl_remove(&iscsit_global.global_ini_list, ini); + nvlist_free(ini->ini_props); + kmem_free(ini, sizeof (*ini)); + iscsit_global_rele(); + } + + for (cfg_ini = cfg->config_ini_list; + cfg_ini != NULL; + cfg_ini = cfg_ini->ini_next) { + ini = kmem_zalloc(sizeof (iscsit_ini_t), KM_SLEEP); + (void) strlcpy(ini->ini_name, cfg_ini->ini_name, + MAX_ISCSI_NODENAMELEN); + (void) nvlist_dup(cfg_ini->ini_properties, &ini->ini_props, + KM_SLEEP); + avl_add(&iscsit_global.global_ini_list, ini); + iscsit_global_hold(); + } + + return (ITCFG_SUCCESS); +} + +int +iscsit_ini_avl_compare(const void *void_ini1, const void *void_ini2) +{ + const iscsit_ini_t *ini1 = void_ini1; + const iscsit_ini_t *ini2 = void_ini2; + int result; + + /* + * Sort by ISID first then TSIH + */ + result = strcmp(ini1->ini_name, ini2->ini_name); + if (result < 0) { + return (-1); + } else if (result > 0) { + return (1); + } + + return (0); +} + +iscsit_ini_t * +iscsit_ini_lookup_locked(char *ini_name) +{ + iscsit_ini_t tmp_ini; + iscsit_ini_t *result; + + /* + * Use a dummy target for lookup, filling in all fields used in AVL + * comparison. + */ + (void) strlcpy(tmp_ini.ini_name, ini_name, MAX_ISCSI_NODENAMELEN); + result = avl_find(&iscsit_global.global_ini_list, &tmp_ini, NULL); + + return (result); +} diff --git a/usr/src/uts/common/io/comstar/port/iscsit/radius_auth.h b/usr/src/uts/common/io/comstar/port/iscsit/radius_auth.h new file mode 100644 index 000000000000..d77b2d23ca41 --- /dev/null +++ b/usr/src/uts/common/io/comstar/port/iscsit/radius_auth.h @@ -0,0 +1,71 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#ifndef _RADIUS_AUTH_H +#define _RADIUS_AUTH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/* + * Function: iscsit_radius_chap_validate + * + * Description: To validate a target response given the + * associated challenge via the specified + * RADIUS server. + * + * Arguments: + * target_chap_name - The CHAP name of the target being authenticated. + * initiator_chap_name - The CHAP name of the authenticating initiator. + * challenge - The CHAP challenge to which the target responded. + * target_response - The target's CHAP response to be validated. + * identifier - The identifier associated with the CHAP challenge. + * radius_server_ip_address - The IP address of the RADIUS server. + * radius_server_port - The port number of the RADIUS server. + * radius_shared_secret - The shared secret for accessing the RADIUS server. + * radius_shared_secret_len - The length of the shared secret. + * + * Return: See chap_validation_status_type. + */ +chap_validation_status_type +iscsit_radius_chap_validate(char *target_chap_name, + char *initiator_chap_name, + uint8_t *challenge, + uint32_t challenge_length, + uint8_t *target_response, + uint32_t response_length, + uint8_t identifier, + iscsi_ipaddr_t rad_svr_ip_addr, + uint32_t rad_svr_port, + uint8_t *rad_svr_shared_secret, + uint32_t rad_svr_shared_secret_len); +#ifdef __cplusplus +} +#endif + +#endif /* _RADIUS_AUTH_H */ diff --git a/usr/src/uts/common/io/idm/idm.c b/usr/src/uts/common/io/idm/idm.c new file mode 100644 index 000000000000..bf95bf14816d --- /dev/null +++ b/usr/src/uts/common/io/idm/idm.c @@ -0,0 +1,2131 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#define IDM_NAME_VERSION "iSCSI Data Mover" + +extern struct mod_ops mod_miscops; +extern struct mod_ops mod_miscops; + +static struct modlmisc modlmisc = { + &mod_miscops, /* Type of module */ + IDM_NAME_VERSION +}; + +static struct modlinkage modlinkage = { + MODREV_1, (void *)&modlmisc, NULL +}; + +extern int idm_task_compare(const void *t1, const void *t2); +extern void idm_wd_thread(void *arg); + +static int _idm_init(void); +static int _idm_fini(void); +static void idm_buf_bind_in_locked(idm_task_t *idt, idm_buf_t *buf); +static void idm_buf_bind_out_locked(idm_task_t *idt, idm_buf_t *buf); +static void idm_buf_unbind_in_locked(idm_task_t *idt, idm_buf_t *buf); +static void idm_buf_unbind_out_locked(idm_task_t *idt, idm_buf_t *buf); +static void idm_task_abort_one(idm_conn_t *ic, idm_task_t *idt, + idm_abort_type_t abort_type); +static void idm_task_aborted(idm_task_t *idt, idm_status_t status); + +boolean_t idm_conn_logging = 0; +boolean_t idm_svc_logging = 0; + +/* + * Potential tuneable for the maximum number of tasks. Default to + * IDM_TASKIDS_MAX + */ + +uint32_t idm_max_taskids = IDM_TASKIDS_MAX; + +/* + * Global list of transport handles + * These are listed in preferential order, so we can simply take the + * first "it_conn_is_capable" hit. Note also that the order maps to + * the order of the idm_transport_type_t list. + */ +idm_transport_t idm_transport_list[] = { + + /* iSER on InfiniBand transport handle */ + {IDM_TRANSPORT_TYPE_ISER, /* type */ + "/devices/ib/iser@0:iser", /* device path */ + NULL, /* LDI handle */ + NULL, /* transport ops */ + NULL}, /* transport caps */ + + /* IDM native sockets transport handle */ + {IDM_TRANSPORT_TYPE_SOCKETS, /* type */ + NULL, /* device path */ + NULL, /* LDI handle */ + NULL, /* transport ops */ + NULL} /* transport caps */ + +}; + +int +_init(void) +{ + int rc; + + if ((rc = _idm_init()) != 0) { + return (rc); + } + + return (mod_install(&modlinkage)); +} + +int +_fini(void) +{ + int rc; + + if ((rc = _idm_fini()) != 0) { + return (rc); + } + + if ((rc = mod_remove(&modlinkage)) != 0) { + return (rc); + } + + return (rc); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +/* + * idm_transport_register() + * + * Provides a mechanism for an IDM transport driver to register its + * transport ops and caps with the IDM kernel module. Invoked during + * a transport driver's attach routine. + */ +idm_status_t +idm_transport_register(idm_transport_attr_t *attr) +{ + ASSERT(attr->it_ops != NULL); + ASSERT(attr->it_caps != NULL); + + switch (attr->type) { + /* All known non-native transports here; for now, iSER */ + case IDM_TRANSPORT_TYPE_ISER: + idm_transport_list[attr->type].it_ops = attr->it_ops; + idm_transport_list[attr->type].it_caps = attr->it_caps; + return (IDM_STATUS_SUCCESS); + + default: + cmn_err(CE_NOTE, "idm: unknown transport type (0x%x) in " + "idm_transport_register", attr->type); + return (IDM_STATUS_SUCCESS); + } +} + +/* + * idm_ini_conn_create + * + * This function is invoked by the iSCSI layer to create a connection context. + * This does not actually establish the socket connection. + * + * cr - Connection request parameters + * new_con - Output parameter that contains the new request if successful + * + */ +idm_status_t +idm_ini_conn_create(idm_conn_req_t *cr, idm_conn_t **new_con) +{ + idm_transport_t *it; + idm_conn_t *ic; + int rc; + + it = idm_transport_lookup(cr); + +retry: + ic = idm_conn_create_common(CONN_TYPE_INI, it->it_type, + &cr->icr_conn_ops); + + bcopy(&cr->cr_ini_dst_addr, &ic->ic_ini_dst_addr, + sizeof (cr->cr_ini_dst_addr)); + + /* create the transport-specific connection components */ + rc = it->it_ops->it_ini_conn_create(cr, ic); + if (rc != IDM_STATUS_SUCCESS) { + /* cleanup the failed connection */ + idm_conn_destroy_common(ic); + kmem_free(ic, sizeof (idm_conn_t)); + + /* + * It is possible for an IB client to connect to + * an ethernet-only client via an IB-eth gateway. + * Therefore, if we are attempting to use iSER and + * fail, retry with sockets before ultimately + * failing the connection. + */ + if (it->it_type == IDM_TRANSPORT_TYPE_ISER) { + it = &idm_transport_list[IDM_TRANSPORT_TYPE_SOCKETS]; + goto retry; + } + + return (IDM_STATUS_FAIL); + } + + *new_con = ic; + + mutex_enter(&idm.idm_global_mutex); + list_insert_tail(&idm.idm_ini_conn_list, ic); + mutex_exit(&idm.idm_global_mutex); + + return (IDM_STATUS_SUCCESS); +} + +/* + * idm_ini_conn_destroy + * + * Releases any resources associated with the connection. This is the + * complement to idm_ini_conn_create. + * ic - idm_conn_t structure representing the relevant connection + * + */ +void +idm_ini_conn_destroy(idm_conn_t *ic) +{ + mutex_enter(&idm.idm_global_mutex); + list_remove(&idm.idm_ini_conn_list, ic); + mutex_exit(&idm.idm_global_mutex); + + ic->ic_transport_ops->it_ini_conn_destroy(ic); + idm_conn_destroy_common(ic); +} + +/* + * idm_ini_conn_connect + * + * Establish connection to the remote system identified in idm_conn_t. + * The connection parameters including the remote IP address were established + * in the call to idm_ini_conn_create. + * + * ic - idm_conn_t structure representing the relevant connection + * + * Returns success if the connection was established, otherwise some kind + * of meaningful error code. + * + * Upon return the initiator can send a "login" request when it is ready. + */ +idm_status_t +idm_ini_conn_connect(idm_conn_t *ic) +{ + idm_status_t rc; + + rc = idm_conn_sm_init(ic); + if (rc != IDM_STATUS_SUCCESS) { + return (ic->ic_conn_sm_status); + } + /* Kick state machine */ + idm_conn_event(ic, CE_CONNECT_REQ, NULL); + + /* Wait for login flag */ + mutex_enter(&ic->ic_state_mutex); + while (!(ic->ic_state_flags & CF_LOGIN_READY) && + !(ic->ic_state_flags & CF_ERROR)) { + cv_wait(&ic->ic_state_cv, &ic->ic_state_mutex); + } + mutex_exit(&ic->ic_state_mutex); + + if (ic->ic_state_flags & CF_ERROR) { + /* ic->ic_conn_sm_status will contains failure status */ + return (ic->ic_conn_sm_status); + } + + /* Ready to login */ + ASSERT(ic->ic_state_flags & CF_LOGIN_READY); + (void) idm_notify_client(ic, CN_READY_FOR_LOGIN, NULL); + + return (IDM_STATUS_SUCCESS); +} + +/* + * idm_ini_conn_sm_fini_task() + * + * Dispatch a thread on the global taskq to tear down an initiator connection's + * state machine. Note: We cannot do this from the disconnect thread as we will + * end up in a situation wherein the thread is running on a taskq that it then + * attempts to destroy. + */ +static void +idm_ini_conn_sm_fini_task(void *ic_void) +{ + idm_conn_sm_fini((idm_conn_t *)ic_void); +} + +/* + * idm_ini_conn_disconnect + * + * Forces a connection (previously established using idm_ini_conn_connect) + * to perform a controlled shutdown, cleaning up any outstanding requests. + * + * ic - idm_conn_t structure representing the relevant connection + * + * This is synchronous and it will return when the connection has been + * properly shutdown. + */ +/* ARGSUSED */ +void +idm_ini_conn_disconnect(idm_conn_t *ic) +{ + mutex_enter(&ic->ic_state_mutex); + + if (ic->ic_state_flags == 0) { + /* already disconnected */ + mutex_exit(&ic->ic_state_mutex); + return; + } + ic->ic_state_flags = 0; + ic->ic_conn_sm_status = 0; + mutex_exit(&ic->ic_state_mutex); + + /* invoke the transport-specific conn_destroy */ + (void) ic->ic_transport_ops->it_ini_conn_disconnect(ic); + + /* teardown the connection sm */ + (void) taskq_dispatch(idm.idm_global_taskq, &idm_ini_conn_sm_fini_task, + (void *)ic, TQ_SLEEP); +} + +/* + * idm_tgt_svc_create + * + * The target calls this service to obtain a service context for each available + * transport, starting a service of each type related to the IP address and port + * passed. The idm_svc_req_t contains the service parameters. + */ +idm_status_t +idm_tgt_svc_create(idm_svc_req_t *sr, idm_svc_t **new_svc) +{ + idm_transport_type_t type; + idm_transport_t *it; + idm_svc_t *is; + int rc; + + *new_svc = NULL; + is = kmem_zalloc(sizeof (idm_svc_t), KM_SLEEP); + + /* Initialize transport-agnostic components of the service handle */ + is->is_svc_req = *sr; + mutex_init(&is->is_mutex, NULL, MUTEX_DEFAULT, NULL); + cv_init(&is->is_cv, NULL, CV_DEFAULT, NULL); + mutex_init(&is->is_count_mutex, NULL, MUTEX_DEFAULT, NULL); + cv_init(&is->is_count_cv, NULL, CV_DEFAULT, NULL); + idm_refcnt_init(&is->is_refcnt, is); + + /* + * Make sure all available transports are setup. We call this now + * instead of at initialization time in case IB has become available + * since we started (hotplug, etc). + */ + idm_transport_setup(sr->sr_li); + + /* + * Loop through the transports, configuring the transport-specific + * components of each one. + */ + for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) { + + it = &idm_transport_list[type]; + /* + * If it_ops is NULL then the transport is unconfigured + * and we shouldn't try to start the service. + */ + if (it->it_ops == NULL) { + continue; + } + + rc = it->it_ops->it_tgt_svc_create(sr, is); + if (rc != IDM_STATUS_SUCCESS) { + /* Teardown any configured services */ + while (type--) { + it = &idm_transport_list[type]; + if (it->it_ops == NULL) { + continue; + } + it->it_ops->it_tgt_svc_destroy(is); + } + /* Free the svc context and return */ + kmem_free(is, sizeof (idm_svc_t)); + return (rc); + } + } + + *new_svc = is; + + mutex_enter(&idm.idm_global_mutex); + list_insert_tail(&idm.idm_tgt_svc_list, is); + mutex_exit(&idm.idm_global_mutex); + + return (IDM_STATUS_SUCCESS); +} + +/* + * idm_tgt_svc_destroy + * + * is - idm_svc_t returned by the call to idm_tgt_svc_create + * + * Cleanup any resources associated with the idm_svc_t. + */ +void +idm_tgt_svc_destroy(idm_svc_t *is) +{ + idm_transport_type_t type; + idm_transport_t *it; + + mutex_enter(&idm.idm_global_mutex); + /* remove this service from the global list */ + list_remove(&idm.idm_tgt_svc_list, is); + /* wakeup any waiters for service change */ + cv_broadcast(&idm.idm_tgt_svc_cv); + mutex_exit(&idm.idm_global_mutex); + + /* tear down the svc resources */ + idm_refcnt_destroy(&is->is_refcnt); + cv_destroy(&is->is_count_cv); + mutex_destroy(&is->is_count_mutex); + cv_destroy(&is->is_cv); + mutex_destroy(&is->is_mutex); + + /* teardown each transport-specific service */ + for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) { + it = &idm_transport_list[type]; + if (it->it_ops == NULL) { + continue; + } + + it->it_ops->it_tgt_svc_destroy(is); + } + + /* free the svc handle */ + kmem_free(is, sizeof (idm_svc_t)); +} + +void +idm_tgt_svc_hold(idm_svc_t *is) +{ + idm_refcnt_hold(&is->is_refcnt); +} + +void +idm_tgt_svc_rele_and_destroy(idm_svc_t *is) +{ + idm_refcnt_rele_and_destroy(&is->is_refcnt, + (idm_refcnt_cb_t *)&idm_tgt_svc_destroy); +} + +/* + * idm_tgt_svc_online + * + * is - idm_svc_t returned by the call to idm_tgt_svc_create + * + * Online each transport service, as we want this target to be accessible + * via any configured transport. + * + * When the initiator establishes a new connection to the target, IDM will + * call the "new connect" callback defined in the idm_svc_req_t structure + * and it will pass an idm_conn_t structure representing that new connection. + */ +idm_status_t +idm_tgt_svc_online(idm_svc_t *is) +{ + + idm_transport_type_t type; + idm_transport_t *it; + int rc; + int svc_found; + + mutex_enter(&is->is_mutex); + /* Walk through each of the transports and online them */ + if (is->is_online == 0) { + svc_found = 0; + for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) { + it = &idm_transport_list[type]; + if (it->it_ops == NULL) { + /* transport is not registered */ + continue; + } + + mutex_exit(&is->is_mutex); + rc = it->it_ops->it_tgt_svc_online(is); + mutex_enter(&is->is_mutex); + if (rc == IDM_STATUS_SUCCESS) { + /* We have at least one service running. */ + svc_found = 1; + } + } + } else { + svc_found = 1; + } + if (svc_found) + is->is_online++; + mutex_exit(&is->is_mutex); + + return (svc_found ? IDM_STATUS_SUCCESS : IDM_STATUS_FAIL); +} + +/* + * idm_tgt_svc_offline + * + * is - idm_svc_t returned by the call to idm_tgt_svc_create + * + * Shutdown any online target services. + */ +void +idm_tgt_svc_offline(idm_svc_t *is) +{ + idm_transport_type_t type; + idm_transport_t *it; + + mutex_enter(&is->is_mutex); + is->is_online--; + if (is->is_online == 0) { + /* Walk through each of the transports and offline them */ + for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) { + it = &idm_transport_list[type]; + if (it->it_ops == NULL) { + /* transport is not registered */ + continue; + } + + mutex_exit(&is->is_mutex); + it->it_ops->it_tgt_svc_offline(is); + mutex_enter(&is->is_mutex); + } + } + mutex_exit(&is->is_mutex); +} + +/* + * idm_tgt_svc_lookup + * + * Lookup a service instance listening on the specified port + */ + +idm_svc_t * +idm_tgt_svc_lookup(uint16_t port) +{ + idm_svc_t *result; + +retry: + mutex_enter(&idm.idm_global_mutex); + for (result = list_head(&idm.idm_tgt_svc_list); + result != NULL; + result = list_next(&idm.idm_tgt_svc_list, result)) { + if (result->is_svc_req.sr_port == port) { + if (result->is_online == 0) { + /* + * A service exists on this port, but it + * is going away, wait for it to cleanup. + */ + cv_wait(&idm.idm_tgt_svc_cv, + &idm.idm_global_mutex); + mutex_exit(&idm.idm_global_mutex); + goto retry; + } + idm_tgt_svc_hold(result); + mutex_exit(&idm.idm_global_mutex); + return (result); + } + } + mutex_exit(&idm.idm_global_mutex); + + return (NULL); +} + +/* + * idm_negotiate_key_values() + * Give IDM level a chance to negotiate any login parameters it should own. + * -- leave unhandled parameters alone on request_nvl + * -- move all handled parameters to response_nvl with an appropriate response + * -- also add an entry to negotiated_nvl for any accepted parameters + */ +kv_status_t +idm_negotiate_key_values(idm_conn_t *ic, nvlist_t *request_nvl, + nvlist_t *response_nvl, nvlist_t *negotiated_nvl) +{ + ASSERT(ic->ic_transport_ops != NULL); + return (ic->ic_transport_ops->it_negotiate_key_values(ic, + request_nvl, response_nvl, negotiated_nvl)); +} + +/* + * idm_notice_key_values() + * Activate at the IDM level any parameters that have been negotiated. + * Passes the set of key value pairs to the transport for activation. + * This will be invoked as the connection is entering full-feature mode. + */ +idm_status_t +idm_notice_key_values(idm_conn_t *ic, nvlist_t *negotiated_nvl) +{ + ASSERT(ic->ic_transport_ops != NULL); + return (ic->ic_transport_ops->it_notice_key_values(ic, + negotiated_nvl)); +} + +/* + * idm_buf_tx_to_ini + * + * This is IDM's implementation of the 'Put_Data' operational primitive. + * + * This function is invoked by a target iSCSI layer to request its local + * Datamover layer to transmit the Data-In PDU to the peer iSCSI layer + * on the remote iSCSI node. The I/O buffer represented by 'idb' is + * transferred to the initiator associated with task 'idt'. The connection + * info, contents of the Data-In PDU header, the DataDescriptorIn, BHS, + * and the callback (idb->idb_buf_cb) at transfer completion are + * provided as input. + * + * This data transfer takes place transparently to the remote iSCSI layer, + * i.e. without its participation. + * + * Using sockets, IDM implements the data transfer by segmenting the data + * buffer into appropriately sized iSCSI PDUs and transmitting them to the + * initiator. iSER performs the transfer using RDMA write. + * + */ +idm_status_t +idm_buf_tx_to_ini(idm_task_t *idt, idm_buf_t *idb, + uint32_t offset, uint32_t xfer_len, + idm_buf_cb_t idb_buf_cb, void *cb_arg) +{ + idm_status_t rc; + + idb->idb_bufoffset = offset; + idb->idb_xfer_len = xfer_len; + idb->idb_buf_cb = idb_buf_cb; + idb->idb_cb_arg = cb_arg; + + mutex_enter(&idt->idt_mutex); + switch (idt->idt_state) { + case TASK_ACTIVE: + idt->idt_tx_to_ini_start++; + idm_task_hold(idt); + idm_buf_bind_in_locked(idt, idb); + idb->idb_in_transport = B_TRUE; + rc = (*idt->idt_ic->ic_transport_ops->it_buf_tx_to_ini) + (idt, idb); + return (rc); + + case TASK_SUSPENDING: + case TASK_SUSPENDED: + /* + * Bind buffer but don't start a transfer since the task + * is suspended + */ + idm_buf_bind_in_locked(idt, idb); + mutex_exit(&idt->idt_mutex); + return (IDM_STATUS_SUCCESS); + + case TASK_ABORTING: + case TASK_ABORTED: + /* + * Once the task is aborted, any buffers added to the + * idt_inbufv will never get cleaned up, so just return + * SUCCESS. The buffer should get cleaned up by the + * client or framework once task_aborted has completed. + */ + mutex_exit(&idt->idt_mutex); + return (IDM_STATUS_SUCCESS); + + default: + ASSERT(0); + break; + } + mutex_exit(&idt->idt_mutex); + + return (IDM_STATUS_FAIL); +} + +/* + * idm_buf_rx_from_ini + * + * This is IDM's implementation of the 'Get_Data' operational primitive. + * + * This function is invoked by a target iSCSI layer to request its local + * Datamover layer to retrieve certain data identified by the R2T PDU from the + * peer iSCSI layer on the remote node. The retrieved Data-Out PDU will be + * mapped to the respective buffer by the task tags (ITT & TTT). + * The connection information, contents of an R2T PDU, DataDescriptor, BHS, and + * the callback (idb->idb_buf_cb) notification for data transfer completion are + * are provided as input. + * + * When an iSCSI node sends an R2T PDU to its local Datamover layer, the local + * Datamover layer, the local and remote Datamover layers transparently bring + * about the data transfer requested by the R2T PDU, without the participation + * of the iSCSI layers. + * + * Using sockets, IDM transmits an R2T PDU for each buffer and the rx_data_out() + * assembles the Data-Out PDUs into the buffer. iSER uses RDMA read. + * + */ +idm_status_t +idm_buf_rx_from_ini(idm_task_t *idt, idm_buf_t *idb, + uint32_t offset, uint32_t xfer_len, + idm_buf_cb_t idb_buf_cb, void *cb_arg) +{ + idm_status_t rc; + + idb->idb_bufoffset = offset; + idb->idb_xfer_len = xfer_len; + idb->idb_buf_cb = idb_buf_cb; + idb->idb_cb_arg = cb_arg; + + /* + * "In" buf list is for "Data In" PDU's, "Out" buf list is for + * "Data Out" PDU's + */ + mutex_enter(&idt->idt_mutex); + switch (idt->idt_state) { + case TASK_ACTIVE: + idt->idt_rx_from_ini_start++; + idm_task_hold(idt); + idm_buf_bind_out_locked(idt, idb); + idb->idb_in_transport = B_TRUE; + rc = (*idt->idt_ic->ic_transport_ops->it_buf_rx_from_ini) + (idt, idb); + return (rc); + case TASK_SUSPENDING: + case TASK_SUSPENDED: + case TASK_ABORTING: + case TASK_ABORTED: + /* + * Bind buffer but don't start a transfer since the task + * is suspended + */ + idm_buf_bind_out_locked(idt, idb); + mutex_exit(&idt->idt_mutex); + return (IDM_STATUS_SUCCESS); + default: + ASSERT(0); + break; + } + mutex_exit(&idt->idt_mutex); + + return (IDM_STATUS_FAIL); +} + +/* + * idm_buf_tx_to_ini_done + * + * The transport calls this after it has completed a transfer requested by + * a call to transport_buf_tx_to_ini + * + * Caller holds idt->idt_mutex, idt->idt_mutex is released before returning. + * idt may be freed after the call to idb->idb_buf_cb. + */ +void +idm_buf_tx_to_ini_done(idm_task_t *idt, idm_buf_t *idb, idm_status_t status) +{ + ASSERT(mutex_owned(&idt->idt_mutex)); + idb->idb_in_transport = B_FALSE; + idb->idb_tx_thread = B_FALSE; + idt->idt_tx_to_ini_done++; + + /* + * idm_refcnt_rele may cause TASK_SUSPENDING --> TASK_SUSPENDED or + * TASK_ABORTING --> TASK_ABORTED transistion if the refcount goes + * to 0. + */ + idm_task_rele(idt); + idb->idb_status = status; + + switch (idt->idt_state) { + case TASK_ACTIVE: + idm_buf_unbind_in_locked(idt, idb); + mutex_exit(&idt->idt_mutex); + (*idb->idb_buf_cb)(idb, status); + return; + case TASK_SUSPENDING: + case TASK_SUSPENDED: + case TASK_ABORTING: + case TASK_ABORTED: + /* + * To keep things simple we will ignore the case where the + * transfer was successful and leave all buffers bound to the + * task. This allows us to also ignore the case where we've + * been asked to abort a task but the last transfer of the + * task has completed. IDM has no idea whether this was, in + * fact, the last transfer of the task so it would be difficult + * to handle this case. Everything should get sorted out again + * after task reassignment is complete. + * + * In the case of TASK_ABORTING we could conceivably call the + * buffer callback here but the timing of when the client's + * client_task_aborted callback is invoked vs. when the client's + * buffer callback gets invoked gets sticky. We don't want + * the client to here from us again after the call to + * client_task_aborted() but we don't want to give it a bunch + * of failed buffer transfers until we've called + * client_task_aborted(). Instead we'll just leave all the + * buffers bound and allow the client to cleanup. + */ + break; + default: + ASSERT(0); + } + mutex_exit(&idt->idt_mutex); +} + +/* + * idm_buf_rx_from_ini_done + * + * The transport calls this after it has completed a transfer requested by + * a call totransport_buf_tx_to_ini + * + * Caller holds idt->idt_mutex, idt->idt_mutex is released before returning. + * idt may be freed after the call to idb->idb_buf_cb. + */ +void +idm_buf_rx_from_ini_done(idm_task_t *idt, idm_buf_t *idb, idm_status_t status) +{ + ASSERT(mutex_owned(&idt->idt_mutex)); + idb->idb_in_transport = B_FALSE; + idt->idt_rx_from_ini_done++; + + /* + * idm_refcnt_rele may cause TASK_SUSPENDING --> TASK_SUSPENDED or + * TASK_ABORTING --> TASK_ABORTED transistion if the refcount goes + * to 0. + */ + idm_task_rele(idt); + idb->idb_status = status; + + switch (idt->idt_state) { + case TASK_ACTIVE: + idm_buf_unbind_out_locked(idt, idb); + mutex_exit(&idt->idt_mutex); + (*idb->idb_buf_cb)(idb, status); + return; + case TASK_SUSPENDING: + case TASK_SUSPENDED: + case TASK_ABORTING: + case TASK_ABORTED: + /* + * To keep things simple we will ignore the case where the + * transfer was successful and leave all buffers bound to the + * task. This allows us to also ignore the case where we've + * been asked to abort a task but the last transfer of the + * task has completed. IDM has no idea whether this was, in + * fact, the last transfer of the task so it would be difficult + * to handle this case. Everything should get sorted out again + * after task reassignment is complete. + * + * In the case of TASK_ABORTING we could conceivably call the + * buffer callback here but the timing of when the client's + * client_task_aborted callback is invoked vs. when the client's + * buffer callback gets invoked gets sticky. We don't want + * the client to here from us again after the call to + * client_task_aborted() but we don't want to give it a bunch + * of failed buffer transfers until we've called + * client_task_aborted(). Instead we'll just leave all the + * buffers bound and allow the client to cleanup. + */ + break; + default: + ASSERT(0); + } + mutex_exit(&idt->idt_mutex); +} + +/* + * idm_buf_alloc + * + * Allocates a buffer handle and registers it for use with the transport + * layer. If a buffer is not passed on bufptr, the buffer will be allocated + * as well as the handle. + * + * ic - connection on which the buffer will be transferred + * bufptr - allocate memory for buffer if NULL, else assign to buffer + * buflen - length of buffer + * + * Returns idm_buf_t handle if successful, otherwise NULL + */ +idm_buf_t * +idm_buf_alloc(idm_conn_t *ic, void *bufptr, uint64_t buflen) +{ + idm_buf_t *buf = NULL; + int rc; + + ASSERT(ic != NULL); + ASSERT(idm.idm_buf_cache != NULL); + ASSERT(buflen > 0); + + /* Don't allocate new buffers if we are not in FFP */ + mutex_enter(&ic->ic_state_mutex); + if (!ic->ic_ffp) { + mutex_exit(&ic->ic_state_mutex); + return (NULL); + } + + + idm_conn_hold(ic); + mutex_exit(&ic->ic_state_mutex); + + buf = kmem_cache_alloc(idm.idm_buf_cache, KM_NOSLEEP); + if (buf == NULL) { + idm_conn_rele(ic); + return (NULL); + } + + buf->idb_ic = ic; + buf->idb_buflen = buflen; + buf->idb_exp_offset = 0; + buf->idb_bufoffset = 0; + buf->idb_xfer_len = 0; + buf->idb_magic = IDM_BUF_MAGIC; + + /* + * If bufptr is NULL, we have an implicit request to allocate + * memory for this IDM buffer handle and register it for use + * with the transport. To simplify this, and to give more freedom + * to the transport layer for it's own buffer management, both of + * these actions will take place in the transport layer. + * If bufptr is set, then the caller has allocated memory (or more + * likely it's been passed from an upper layer), and we need only + * register the buffer for use with the transport layer. + */ + if (bufptr == NULL) { + /* + * Allocate a buffer from the transport layer (which + * will also register the buffer for use). + */ + rc = ic->ic_transport_ops->it_buf_alloc(buf, buflen); + if (rc != 0) { + idm_conn_rele(ic); + kmem_cache_free(idm.idm_buf_cache, buf); + return (NULL); + } + /* Set the bufalloc'd flag */ + buf->idb_bufalloc = B_TRUE; + } else { + /* + * Set the passed bufptr into the buf handle, and + * register the handle with the transport layer. + */ + buf->idb_buf = bufptr; + + rc = ic->ic_transport_ops->it_buf_setup(buf); + if (rc != 0) { + idm_conn_rele(ic); + kmem_cache_free(idm.idm_buf_cache, buf); + return (NULL); + } + /* Ensure bufalloc'd flag is unset */ + buf->idb_bufalloc = B_FALSE; + } + + return (buf); + +} + +/* + * idm_buf_free + * + * Release a buffer handle along with the associated buffer that was allocated + * or assigned with idm_buf_alloc + */ +void +idm_buf_free(idm_buf_t *buf) +{ + idm_conn_t *ic = buf->idb_ic; + + + buf->idb_task_binding = NULL; + + if (buf->idb_bufalloc) { + ic->ic_transport_ops->it_buf_free(buf); + } else { + ic->ic_transport_ops->it_buf_teardown(buf); + } + kmem_cache_free(idm.idm_buf_cache, buf); + idm_conn_rele(ic); +} + +/* + * idm_buf_bind_in + * + * This function associates a buffer with a task. This is only for use by the + * iSCSI initiator that will have only one buffer per transfer direction + * + */ +void +idm_buf_bind_in(idm_task_t *idt, idm_buf_t *buf) +{ + mutex_enter(&idt->idt_mutex); + idm_buf_bind_in_locked(idt, buf); + mutex_exit(&idt->idt_mutex); +} + +static void +idm_buf_bind_in_locked(idm_task_t *idt, idm_buf_t *buf) +{ + buf->idb_task_binding = idt; + buf->idb_ic = idt->idt_ic; + idm_listbuf_insert(&idt->idt_inbufv, buf); +} + +void +idm_buf_bind_out(idm_task_t *idt, idm_buf_t *buf) +{ + mutex_enter(&idt->idt_mutex); + idm_buf_bind_out_locked(idt, buf); + mutex_exit(&idt->idt_mutex); +} + +static void +idm_buf_bind_out_locked(idm_task_t *idt, idm_buf_t *buf) +{ + buf->idb_task_binding = idt; + buf->idb_ic = idt->idt_ic; + idm_listbuf_insert(&idt->idt_outbufv, buf); +} + +void +idm_buf_unbind_in(idm_task_t *idt, idm_buf_t *buf) +{ + mutex_enter(&idt->idt_mutex); + idm_buf_unbind_in_locked(idt, buf); + mutex_exit(&idt->idt_mutex); +} + +static void +idm_buf_unbind_in_locked(idm_task_t *idt, idm_buf_t *buf) +{ + list_remove(&idt->idt_inbufv, buf); +} + +void +idm_buf_unbind_out(idm_task_t *idt, idm_buf_t *buf) +{ + mutex_enter(&idt->idt_mutex); + idm_buf_unbind_out_locked(idt, buf); + mutex_exit(&idt->idt_mutex); +} + +static void +idm_buf_unbind_out_locked(idm_task_t *idt, idm_buf_t *buf) +{ + list_remove(&idt->idt_outbufv, buf); +} + +/* + * idm_buf_find() will lookup the idm_buf_t based on the relative offset in the + * iSCSI PDU + */ +idm_buf_t * +idm_buf_find(void *lbuf, size_t data_offset) +{ + idm_buf_t *idb; + list_t *lst = (list_t *)lbuf; + + /* iterate through the list to find the buffer */ + for (idb = list_head(lst); idb != NULL; idb = list_next(lst, idb)) { + + ASSERT((idb->idb_ic->ic_conn_type == CONN_TYPE_TGT) || + (idb->idb_bufoffset == 0)); + + if ((data_offset >= idb->idb_bufoffset) && + (data_offset < (idb->idb_bufoffset + idb->idb_buflen))) { + + return (idb); + } + } + + return (NULL); +} + +/* + * idm_task_alloc + * + * This function will allocate a idm_task_t structure. A task tag is also + * generated and saved in idt_tt. The task is not active. + */ +idm_task_t * +idm_task_alloc(idm_conn_t *ic) +{ + idm_task_t *idt; + + ASSERT(ic != NULL); + + /* Don't allocate new tasks if we are not in FFP */ + mutex_enter(&ic->ic_state_mutex); + if (!ic->ic_ffp) { + mutex_exit(&ic->ic_state_mutex); + return (NULL); + } + idt = kmem_cache_alloc(idm.idm_task_cache, KM_NOSLEEP); + if (idt == NULL) { + mutex_exit(&ic->ic_state_mutex); + return (NULL); + } + + ASSERT(list_is_empty(&idt->idt_inbufv)); + ASSERT(list_is_empty(&idt->idt_outbufv)); + + idm_conn_hold(ic); + mutex_exit(&ic->ic_state_mutex); + + idt->idt_state = TASK_IDLE; + idt->idt_ic = ic; + idt->idt_private = NULL; + idt->idt_exp_datasn = 0; + idt->idt_exp_rttsn = 0; + + return (idt); +} + +/* + * idm_task_start + * + * Add the task to an AVL tree to notify IDM about a new task. The caller + * sets up the idm_task_t structure with a prior call to idm_task_alloc(). + * The task service does not function as a task/work engine, it is the + * responsibility of the initiator to start the data transfer and free the + * resources. + */ +void +idm_task_start(idm_task_t *idt, uintptr_t handle) +{ + ASSERT(idt != NULL); + + /* mark the task as ACTIVE */ + idt->idt_state = TASK_ACTIVE; + idt->idt_client_handle = handle; + idt->idt_tx_to_ini_start = idt->idt_tx_to_ini_done = + idt->idt_rx_from_ini_start = idt->idt_rx_from_ini_done = 0; +} + +/* + * idm_task_done + * + * This function will remove the task from the AVL tree indicating that the + * task is no longer active. + */ +void +idm_task_done(idm_task_t *idt) +{ + ASSERT(idt != NULL); + ASSERT(idt->idt_refcnt.ir_refcnt == 0); + + idt->idt_state = TASK_IDLE; + idm_refcnt_reset(&idt->idt_refcnt); +} + +/* + * idm_task_free + * + * This function will free the Task Tag and the memory allocated for the task + * idm_task_done should be called prior to this call + */ +void +idm_task_free(idm_task_t *idt) +{ + idm_conn_t *ic = idt->idt_ic; + + ASSERT(idt != NULL); + ASSERT(idt->idt_state == TASK_IDLE); + + /* + * It's possible for items to still be in the idt_inbufv list if + * they were added after idm_task_cleanup was called. We rely on + * STMF to free all buffers associated with the task however STMF + * doesn't know that we have this reference to the buffers. + * Use list_create so that we don't end up with stale references + * to these buffers. + */ + list_create(&idt->idt_inbufv, sizeof (idm_buf_t), + offsetof(idm_buf_t, idb_buflink)); + list_create(&idt->idt_outbufv, sizeof (idm_buf_t), + offsetof(idm_buf_t, idb_buflink)); + + kmem_cache_free(idm.idm_task_cache, idt); + + idm_conn_rele(ic); +} + +/* + * idm_task_find + * + * This function looks up a task by task tag + */ +/*ARGSUSED*/ +idm_task_t * +idm_task_find(idm_conn_t *ic, uint32_t itt, uint32_t ttt) +{ + uint32_t tt, client_handle; + idm_task_t *idt; + + /* + * Must match both itt and ttt. The table is indexed by itt + * for initiator connections and ttt for target connections. + */ + if (IDM_CONN_ISTGT(ic)) { + tt = ttt; + client_handle = itt; + } else { + tt = itt; + client_handle = ttt; + } + + rw_enter(&idm.idm_taskid_table_lock, RW_READER); + if (tt >= idm.idm_taskid_max) { + rw_exit(&idm.idm_taskid_table_lock); + return (NULL); + } + + idt = idm.idm_taskid_table[tt]; + + if (idt != NULL) { + mutex_enter(&idt->idt_mutex); + if ((idt->idt_state != TASK_ACTIVE) || + (IDM_CONN_ISTGT(ic) && + (idt->idt_client_handle != client_handle))) { + /* + * Task is aborting, we don't want any more references. + */ + mutex_exit(&idt->idt_mutex); + rw_exit(&idm.idm_taskid_table_lock); + return (NULL); + } + idm_task_hold(idt); + mutex_exit(&idt->idt_mutex); + } + rw_exit(&idm.idm_taskid_table_lock); + + return (idt); +} + +/* + * idm_task_find_by_handle + * + * This function looks up a task by the client-private idt_client_handle. + * + * This function should NEVER be called in the performance path. It is + * intended strictly for error recovery/task management. + */ +/*ARGSUSED*/ +void * +idm_task_find_by_handle(idm_conn_t *ic, uintptr_t handle) +{ + idm_task_t *idt = NULL; + int idx = 0; + + rw_enter(&idm.idm_taskid_table_lock, RW_READER); + + for (idx = 0; idx < idm.idm_taskid_max; idx++) { + idt = idm.idm_taskid_table[idx]; + + if (idt == NULL) + continue; + + mutex_enter(&idt->idt_mutex); + + if (idt->idt_state != TASK_ACTIVE) { + /* + * Task is either in suspend, abort, or already + * complete. + */ + mutex_exit(&idt->idt_mutex); + continue; + } + + if (idt->idt_client_handle == handle) { + idm_task_hold(idt); + mutex_exit(&idt->idt_mutex); + break; + } + + mutex_exit(&idt->idt_mutex); + } + + rw_exit(&idm.idm_taskid_table_lock); + + if ((idt == NULL) || (idx == idm.idm_taskid_max)) + return (NULL); + + return (idt->idt_private); +} + +void +idm_task_hold(idm_task_t *idt) +{ + idm_refcnt_hold(&idt->idt_refcnt); +} + +void +idm_task_rele(idm_task_t *idt) +{ + idm_refcnt_rele(&idt->idt_refcnt); +} + +void +idm_task_abort(idm_conn_t *ic, idm_task_t *idt, idm_abort_type_t abort_type) +{ + idm_task_t *task; + int idx; + + /* + * Passing NULL as the task indicates that all tasks + * for this connection should be aborted. + */ + if (idt == NULL) { + /* + * Only the connection state machine should ask for + * all tasks to abort and this should never happen in FFP. + */ + ASSERT(!ic->ic_ffp); + rw_enter(&idm.idm_taskid_table_lock, RW_READER); + for (idx = 0; idx < idm.idm_taskid_max; idx++) { + task = idm.idm_taskid_table[idx]; + if (task && (task->idt_state != TASK_IDLE) && + (task->idt_ic == ic)) { + rw_exit(&idm.idm_taskid_table_lock); + idm_task_abort_one(ic, task, abort_type); + rw_enter(&idm.idm_taskid_table_lock, RW_READER); + } + } + rw_exit(&idm.idm_taskid_table_lock); + } else { + idm_task_abort_one(ic, idt, abort_type); + } +} + +static void +idm_task_abort_unref_cb(void *ref) +{ + idm_task_t *idt = ref; + + mutex_enter(&idt->idt_mutex); + switch (idt->idt_state) { + case TASK_SUSPENDING: + idt->idt_state = TASK_SUSPENDED; + mutex_exit(&idt->idt_mutex); + idm_task_aborted(idt, IDM_STATUS_SUSPENDED); + return; + case TASK_ABORTING: + idt->idt_state = TASK_ABORTED; + mutex_exit(&idt->idt_mutex); + idm_task_aborted(idt, IDM_STATUS_ABORTED); + return; + default: + mutex_exit(&idt->idt_mutex); + ASSERT(0); + break; + } +} + +static void +idm_task_abort_one(idm_conn_t *ic, idm_task_t *idt, idm_abort_type_t abort_type) +{ + /* Caller must hold connection mutex */ + mutex_enter(&idt->idt_mutex); + switch (idt->idt_state) { + case TASK_ACTIVE: + switch (abort_type) { + case AT_INTERNAL_SUSPEND: + /* Call transport to release any resources */ + idt->idt_state = TASK_SUSPENDING; + mutex_exit(&idt->idt_mutex); + ic->ic_transport_ops->it_free_task_rsrc(idt); + + /* + * Wait for outstanding references. When all + * references are released the callback will call + * idm_task_aborted(). + */ + idm_refcnt_async_wait_ref(&idt->idt_refcnt, + &idm_task_abort_unref_cb); + return; + case AT_INTERNAL_ABORT: + case AT_TASK_MGMT_ABORT: + idt->idt_state = TASK_ABORTING; + mutex_exit(&idt->idt_mutex); + ic->ic_transport_ops->it_free_task_rsrc(idt); + + /* + * Wait for outstanding references. When all + * references are released the callback will call + * idm_task_aborted(). + */ + idm_refcnt_async_wait_ref(&idt->idt_refcnt, + &idm_task_abort_unref_cb); + return; + default: + ASSERT(0); + } + break; + case TASK_SUSPENDING: + /* Already called transport_free_task_rsrc(); */ + switch (abort_type) { + case AT_INTERNAL_SUSPEND: + /* Already doing it */ + break; + case AT_INTERNAL_ABORT: + case AT_TASK_MGMT_ABORT: + idt->idt_state = TASK_ABORTING; + break; + default: + ASSERT(0); + } + break; + case TASK_SUSPENDED: + /* Already called transport_free_task_rsrc(); */ + switch (abort_type) { + case AT_INTERNAL_SUSPEND: + /* Already doing it */ + break; + case AT_INTERNAL_ABORT: + case AT_TASK_MGMT_ABORT: + idt->idt_state = TASK_ABORTING; + mutex_exit(&idt->idt_mutex); + + /* + * We could probably call idm_task_aborted directly + * here but we may be holding the conn lock. It's + * easier to just switch contexts. Even though + * we shouldn't really have any references we'll + * set the state to TASK_ABORTING instead of + * TASK_ABORTED so we can use the same code path. + */ + idm_refcnt_async_wait_ref(&idt->idt_refcnt, + &idm_task_abort_unref_cb); + return; + default: + ASSERT(0); + } + break; + case TASK_ABORTING: + case TASK_ABORTED: + switch (abort_type) { + case AT_INTERNAL_SUSPEND: + /* We're already past this point... */ + case AT_INTERNAL_ABORT: + case AT_TASK_MGMT_ABORT: + /* Already doing it */ + break; + default: + ASSERT(0); + } + break; + case TASK_COMPLETE: + /* + * In this case, let it go. The status has already been + * sent (which may or may not get successfully transmitted) + * and we don't want to end up in a race between completing + * the status PDU and marking the task suspended. + */ + break; + default: + ASSERT(0); + } + mutex_exit(&idt->idt_mutex); +} + +static void +idm_task_aborted(idm_task_t *idt, idm_status_t status) +{ + (*idt->idt_ic->ic_conn_ops.icb_task_aborted)(idt, status); +} + +void +idm_task_cleanup(idm_task_t *idt) +{ + idm_buf_t *idb, *next_idb; + list_t tmp_buflist; + ASSERT((idt->idt_state == TASK_SUSPENDED) || + (idt->idt_state == TASK_ABORTED)); + + list_create(&tmp_buflist, sizeof (idm_buf_t), + offsetof(idm_buf_t, idb_buflink)); + + /* + * Remove all the buffers from the task and add them to a + * temporary local list -- we do this so that we can hold + * the task lock and prevent the task from going away if + * the client decides to call idm_task_done/idm_task_free. + * This could happen during abort in iscsit. + */ + mutex_enter(&idt->idt_mutex); + for (idb = list_head(&idt->idt_inbufv); + idb != NULL; + idb = next_idb) { + next_idb = list_next(&idt->idt_inbufv, idb); + idm_buf_unbind_in_locked(idt, idb); + list_insert_tail(&tmp_buflist, idb); + } + + for (idb = list_head(&idt->idt_outbufv); + idb != NULL; + idb = next_idb) { + next_idb = list_next(&idt->idt_outbufv, idb); + idm_buf_unbind_out_locked(idt, idb); + list_insert_tail(&tmp_buflist, idb); + } + mutex_exit(&idt->idt_mutex); + + for (idb = list_head(&tmp_buflist); idb != NULL; idb = next_idb) { + next_idb = list_next(&tmp_buflist, idb); + list_remove(&tmp_buflist, idb); + (*idb->idb_buf_cb)(idb, IDM_STATUS_ABORTED); + } + list_destroy(&tmp_buflist); +} + + +/* + * idm_pdu_tx + * + * This is IDM's implementation of the 'Send_Control' operational primitive. + * This function is invoked by an initiator iSCSI layer requesting the transfer + * of a iSCSI command PDU or a target iSCSI layer requesting the transfer of a + * iSCSI response PDU. The PDU will be transmitted as-is by the local Datamover + * layer to the peer iSCSI layer in the remote iSCSI node. The connection info + * and iSCSI PDU-specific qualifiers namely BHS, AHS, DataDescriptor and Size + * are provided as input. + * + */ +void +idm_pdu_tx(idm_pdu_t *pdu) +{ + idm_conn_t *ic = pdu->isp_ic; + iscsi_async_evt_hdr_t *async_evt; + + /* + * If we are in full-featured mode then route SCSI-related + * commands to the appropriate function vector without checking + * the connection state. We will only be in full-feature mode + * when we are in an acceptable state for SCSI PDU's. + * + * We also need to ensure that there are no PDU events outstanding + * on the state machine. Any non-SCSI PDU's received in full-feature + * mode will result in PDU events and until these have been handled + * we need to route all PDU's through the state machine as PDU + * events to maintain ordering. + * + * Note that IDM cannot enter FFP mode until it processes in + * its state machine the last xmit of the login process. + * Hence, checking the IDM_PDU_LOGIN_TX flag here would be + * superfluous. + */ + mutex_enter(&ic->ic_state_mutex); + if (ic->ic_ffp && (ic->ic_pdu_events == 0)) { + mutex_exit(&ic->ic_state_mutex); + switch (IDM_PDU_OPCODE(pdu)) { + case ISCSI_OP_SCSI_RSP: + /* Target only */ + idm_pdu_tx_forward(ic, pdu); + return; + case ISCSI_OP_SCSI_TASK_MGT_RSP: + /* Target only */ + idm_pdu_tx_forward(ic, pdu); + return; + case ISCSI_OP_SCSI_DATA_RSP: + /* Target only */ + idm_pdu_tx_forward(ic, pdu); + return; + case ISCSI_OP_RTT_RSP: + /* Target only */ + idm_pdu_tx_forward(ic, pdu); + return; + case ISCSI_OP_NOOP_IN: + /* Target only */ + idm_pdu_tx_forward(ic, pdu); + return; + case ISCSI_OP_TEXT_RSP: + /* Target only */ + idm_pdu_tx_forward(ic, pdu); + return; + case ISCSI_OP_TEXT_CMD: + case ISCSI_OP_NOOP_OUT: + case ISCSI_OP_SCSI_CMD: + case ISCSI_OP_SCSI_DATA: + case ISCSI_OP_SCSI_TASK_MGT_MSG: + /* Initiator only */ + idm_pdu_tx_forward(ic, pdu); + return; + default: + break; + } + + mutex_enter(&ic->ic_state_mutex); + } + + /* + * Any PDU's processed outside of full-feature mode and non-SCSI + * PDU's in full-feature mode are handled by generating an + * event to the connection state machine. The state machine + * will validate the PDU against the current state and either + * transmit the PDU if the opcode is allowed or handle an + * error if the PDU is not allowed. + * + * This code-path will also generate any events that are implied + * by the PDU opcode. For example a "login response" with success + * status generates a CE_LOGOUT_SUCCESS_SND event. + */ + switch (IDM_PDU_OPCODE(pdu)) { + case ISCSI_OP_LOGIN_CMD: + idm_conn_tx_pdu_event(ic, CE_LOGIN_SND, (uintptr_t)pdu); + break; + case ISCSI_OP_LOGIN_RSP: + idm_parse_login_rsp(ic, pdu, /* Is RX */ B_FALSE); + break; + case ISCSI_OP_LOGOUT_CMD: + idm_parse_logout_req(ic, pdu, /* Is RX */ B_FALSE); + break; + case ISCSI_OP_LOGOUT_RSP: + idm_parse_logout_rsp(ic, pdu, /* Is RX */ B_FALSE); + break; + case ISCSI_OP_ASYNC_EVENT: + async_evt = (iscsi_async_evt_hdr_t *)pdu->isp_hdr; + switch (async_evt->async_event) { + case ISCSI_ASYNC_EVENT_REQUEST_LOGOUT: + idm_conn_tx_pdu_event(ic, CE_ASYNC_LOGOUT_SND, + (uintptr_t)pdu); + break; + case ISCSI_ASYNC_EVENT_DROPPING_CONNECTION: + idm_conn_tx_pdu_event(ic, CE_ASYNC_DROP_CONN_SND, + (uintptr_t)pdu); + break; + case ISCSI_ASYNC_EVENT_DROPPING_ALL_CONNECTIONS: + idm_conn_tx_pdu_event(ic, CE_ASYNC_DROP_ALL_CONN_SND, + (uintptr_t)pdu); + break; + case ISCSI_ASYNC_EVENT_SCSI_EVENT: + case ISCSI_ASYNC_EVENT_PARAM_NEGOTIATION: + default: + idm_conn_tx_pdu_event(ic, CE_MISC_TX, + (uintptr_t)pdu); + break; + } + break; + case ISCSI_OP_SCSI_RSP: + /* Target only */ + idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu); + break; + case ISCSI_OP_SCSI_TASK_MGT_RSP: + /* Target only */ + idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu); + break; + case ISCSI_OP_SCSI_DATA_RSP: + /* Target only */ + idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu); + break; + case ISCSI_OP_RTT_RSP: + /* Target only */ + idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu); + break; + case ISCSI_OP_NOOP_IN: + /* Target only */ + idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu); + break; + case ISCSI_OP_TEXT_RSP: + /* Target only */ + idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu); + break; + /* Initiator only */ + case ISCSI_OP_SCSI_CMD: + case ISCSI_OP_SCSI_TASK_MGT_MSG: + case ISCSI_OP_SCSI_DATA: + case ISCSI_OP_NOOP_OUT: + case ISCSI_OP_TEXT_CMD: + case ISCSI_OP_SNACK_CMD: + case ISCSI_OP_REJECT_MSG: + default: + /* + * Connection state machine will validate these PDU's against + * the current state. A PDU not allowed in the current + * state will cause a protocol error. + */ + idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu); + break; + } + mutex_exit(&ic->ic_state_mutex); +} + +/* + * Allocates a PDU along with memory for header and data. + */ + +idm_pdu_t * +idm_pdu_alloc(uint_t hdrlen, uint_t datalen) +{ + idm_pdu_t *result; + + /* + * IDM clients should cache these structures for performance + * critical paths. We can't cache effectively in IDM because we + * don't know the correct header and data size. + * + * Valid header length is assumed to be hdrlen and valid data + * length is assumed to be datalen. isp_hdrlen and isp_datalen + * can be adjusted after the PDU is returned if necessary. + */ + result = kmem_zalloc(sizeof (idm_pdu_t) + hdrlen + datalen, KM_SLEEP); + result->isp_flags |= IDM_PDU_ALLOC; /* For idm_pdu_free sanity check */ + result->isp_hdr = (iscsi_hdr_t *)(result + 1); /* Ptr. Arithmetic */ + result->isp_hdrlen = hdrlen; + result->isp_hdrbuflen = hdrlen; + result->isp_transport_hdrlen = 0; + result->isp_data = (uint8_t *)result->isp_hdr + hdrlen; + result->isp_datalen = datalen; + result->isp_databuflen = datalen; + result->isp_magic = IDM_PDU_MAGIC; + + return (result); +} + +/* + * Free a PDU previously allocated with idm_pdu_alloc() including any + * header and data space allocated as part of the original request. + * Additional memory regions referenced by subsequent modification of + * the isp_hdr and/or isp_data fields will not be freed. + */ +void +idm_pdu_free(idm_pdu_t *pdu) +{ + /* Make sure the structure was allocated using idm_pdu_alloc() */ + ASSERT(pdu->isp_flags & IDM_PDU_ALLOC); + kmem_free(pdu, + sizeof (idm_pdu_t) + pdu->isp_hdrbuflen + pdu->isp_databuflen); +} + +/* + * Initialize the connection, private and callback fields in a PDU. + */ +void +idm_pdu_init(idm_pdu_t *pdu, idm_conn_t *ic, void *private, idm_pdu_cb_t *cb) +{ + /* + * idm_pdu_complete() will call idm_pdu_free if the callback is + * NULL. This will only work if the PDU was originally allocated + * with idm_pdu_alloc(). + */ + ASSERT((pdu->isp_flags & IDM_PDU_ALLOC) || + (cb != NULL)); + pdu->isp_magic = IDM_PDU_MAGIC; + pdu->isp_ic = ic; + pdu->isp_private = private; + pdu->isp_callback = cb; +} + +/* + * Initialize the header and header length field. This function should + * not be used to adjust the header length in a buffer allocated via + * pdu_pdu_alloc since it overwrites the existing header pointer. + */ +void +idm_pdu_init_hdr(idm_pdu_t *pdu, uint8_t *hdr, uint_t hdrlen) +{ + pdu->isp_hdr = (iscsi_hdr_t *)((void *)hdr); + pdu->isp_hdrlen = hdrlen; +} + +/* + * Initialize the data and data length fields. This function should + * not be used to adjust the data length of a buffer allocated via + * idm_pdu_alloc since it overwrites the existing data pointer. + */ +void +idm_pdu_init_data(idm_pdu_t *pdu, uint8_t *data, uint_t datalen) +{ + pdu->isp_data = data; + pdu->isp_datalen = datalen; +} + +void +idm_pdu_complete(idm_pdu_t *pdu, idm_status_t status) +{ + if (pdu->isp_callback) { + pdu->isp_status = status; + (*pdu->isp_callback)(pdu, status); + } else { + idm_pdu_free(pdu); + } +} + +/* + * State machine auditing + */ + +void +idm_sm_audit_init(sm_audit_buf_t *audit_buf) +{ + bzero(audit_buf, sizeof (sm_audit_buf_t)); + audit_buf->sab_max_index = SM_AUDIT_BUF_MAX_REC - 1; +} + +static +sm_audit_record_t * +idm_sm_audit_common(sm_audit_buf_t *audit_buf, sm_audit_record_type_t r_type, + sm_audit_sm_type_t sm_type, + int current_state) +{ + sm_audit_record_t *sar; + + sar = audit_buf->sab_records; + sar += audit_buf->sab_index; + audit_buf->sab_index++; + audit_buf->sab_index &= audit_buf->sab_max_index; + + sar->sar_type = r_type; + gethrestime(&sar->sar_timestamp); + sar->sar_sm_type = sm_type; + sar->sar_state = current_state; + + return (sar); +} + +void +idm_sm_audit_event(sm_audit_buf_t *audit_buf, + sm_audit_sm_type_t sm_type, int current_state, + int event, uintptr_t event_info) +{ + sm_audit_record_t *sar; + + sar = idm_sm_audit_common(audit_buf, SAR_STATE_EVENT, + sm_type, current_state); + sar->sar_event = event; + sar->sar_event_info = event_info; +} + +void +idm_sm_audit_state_change(sm_audit_buf_t *audit_buf, + sm_audit_sm_type_t sm_type, int current_state, int new_state) +{ + sm_audit_record_t *sar; + + sar = idm_sm_audit_common(audit_buf, SAR_STATE_CHANGE, + sm_type, current_state); + sar->sar_new_state = new_state; +} + + +/* + * Object reference tracking + */ + +void +idm_refcnt_init(idm_refcnt_t *refcnt, void *referenced_obj) +{ + bzero(refcnt, sizeof (*refcnt)); + idm_refcnt_reset(refcnt); + refcnt->ir_referenced_obj = referenced_obj; + bzero(&refcnt->ir_audit_buf, sizeof (refcnt_audit_buf_t)); + refcnt->ir_audit_buf.anb_max_index = REFCNT_AUDIT_BUF_MAX_REC - 1; + mutex_init(&refcnt->ir_mutex, NULL, MUTEX_DEFAULT, NULL); + cv_init(&refcnt->ir_cv, NULL, CV_DEFAULT, NULL); +} + +void +idm_refcnt_destroy(idm_refcnt_t *refcnt) +{ + ASSERT(refcnt->ir_refcnt == 0); + cv_destroy(&refcnt->ir_cv); + mutex_destroy(&refcnt->ir_mutex); +} + +void +idm_refcnt_reset(idm_refcnt_t *refcnt) +{ + refcnt->ir_waiting = REF_NOWAIT; + refcnt->ir_refcnt = 0; +} + +void +idm_refcnt_hold(idm_refcnt_t *refcnt) +{ + /* + * Nothing should take a hold on an object after a call to + * idm_refcnt_wait_ref or idm_refcnd_async_wait_ref + */ + ASSERT(refcnt->ir_waiting == REF_NOWAIT); + + mutex_enter(&refcnt->ir_mutex); + refcnt->ir_refcnt++; + REFCNT_AUDIT(refcnt); + mutex_exit(&refcnt->ir_mutex); +} + +static void +idm_refcnt_unref_task(void *refcnt_void) +{ + idm_refcnt_t *refcnt = refcnt_void; + + REFCNT_AUDIT(refcnt); + (*refcnt->ir_cb)(refcnt->ir_referenced_obj); +} + +void +idm_refcnt_rele(idm_refcnt_t *refcnt) +{ + mutex_enter(&refcnt->ir_mutex); + ASSERT(refcnt->ir_refcnt > 0); + refcnt->ir_refcnt--; + REFCNT_AUDIT(refcnt); + if (refcnt->ir_waiting == REF_NOWAIT) { + /* No one is waiting on this object */ + mutex_exit(&refcnt->ir_mutex); + return; + } + + /* + * Someone is waiting for this object to go idle so check if + * refcnt is 0. Waiting on an object then later grabbing another + * reference is not allowed so we don't need to handle that case. + */ + if (refcnt->ir_refcnt == 0) { + if (refcnt->ir_waiting == REF_WAIT_ASYNC) { + if (taskq_dispatch(idm.idm_global_taskq, + &idm_refcnt_unref_task, refcnt, TQ_SLEEP) == NULL) { + cmn_err(CE_WARN, + "idm_refcnt_rele: Couldn't dispatch task"); + } + } else if (refcnt->ir_waiting == REF_WAIT_SYNC) { + cv_signal(&refcnt->ir_cv); + } + } + mutex_exit(&refcnt->ir_mutex); +} + +void +idm_refcnt_rele_and_destroy(idm_refcnt_t *refcnt, idm_refcnt_cb_t *cb_func) +{ + mutex_enter(&refcnt->ir_mutex); + ASSERT(refcnt->ir_refcnt > 0); + refcnt->ir_refcnt--; + REFCNT_AUDIT(refcnt); + + /* + * Someone is waiting for this object to go idle so check if + * refcnt is 0. Waiting on an object then later grabbing another + * reference is not allowed so we don't need to handle that case. + */ + if (refcnt->ir_refcnt == 0) { + refcnt->ir_cb = cb_func; + refcnt->ir_waiting = REF_WAIT_ASYNC; + if (taskq_dispatch(idm.idm_global_taskq, + &idm_refcnt_unref_task, refcnt, TQ_SLEEP) == NULL) { + cmn_err(CE_WARN, + "idm_refcnt_rele: Couldn't dispatch task"); + } + } + mutex_exit(&refcnt->ir_mutex); +} + +void +idm_refcnt_wait_ref(idm_refcnt_t *refcnt) +{ + mutex_enter(&refcnt->ir_mutex); + refcnt->ir_waiting = REF_WAIT_SYNC; + REFCNT_AUDIT(refcnt); + while (refcnt->ir_refcnt != 0) + cv_wait(&refcnt->ir_cv, &refcnt->ir_mutex); + mutex_exit(&refcnt->ir_mutex); +} + +void +idm_refcnt_async_wait_ref(idm_refcnt_t *refcnt, idm_refcnt_cb_t *cb_func) +{ + mutex_enter(&refcnt->ir_mutex); + refcnt->ir_waiting = REF_WAIT_ASYNC; + refcnt->ir_cb = cb_func; + REFCNT_AUDIT(refcnt); + /* + * It's possible we don't have any references. To make things easier + * on the caller use a taskq to call the callback instead of + * calling it synchronously + */ + if (refcnt->ir_refcnt == 0) { + if (taskq_dispatch(idm.idm_global_taskq, + &idm_refcnt_unref_task, refcnt, TQ_SLEEP) == NULL) { + cmn_err(CE_WARN, + "idm_refcnt_async_wait_ref: " + "Couldn't dispatch task"); + } + } + mutex_exit(&refcnt->ir_mutex); +} + +void +idm_refcnt_destroy_unref_obj(idm_refcnt_t *refcnt, + idm_refcnt_cb_t *cb_func) +{ + mutex_enter(&refcnt->ir_mutex); + if (refcnt->ir_refcnt == 0) { + mutex_exit(&refcnt->ir_mutex); + (*cb_func)(refcnt->ir_referenced_obj); + return; + } + mutex_exit(&refcnt->ir_mutex); +} + +void +idm_conn_hold(idm_conn_t *ic) +{ + idm_refcnt_hold(&ic->ic_refcnt); +} + +void +idm_conn_rele(idm_conn_t *ic) +{ + idm_refcnt_rele(&ic->ic_refcnt); +} + + +static int +_idm_init(void) +{ + /* Initialize the rwlock for the taskid table */ + rw_init(&idm.idm_taskid_table_lock, NULL, RW_DRIVER, NULL); + + /* Initialize the global mutex and taskq */ + mutex_init(&idm.idm_global_mutex, NULL, MUTEX_DEFAULT, NULL); + + cv_init(&idm.idm_tgt_svc_cv, NULL, CV_DEFAULT, NULL); + cv_init(&idm.idm_wd_cv, NULL, CV_DEFAULT, NULL); + + idm.idm_global_taskq = taskq_create("idm_global_taskq", 1, minclsyspri, + 4, 4, TASKQ_PREPOPULATE); + if (idm.idm_global_taskq == NULL) { + cv_destroy(&idm.idm_wd_cv); + cv_destroy(&idm.idm_tgt_svc_cv); + mutex_destroy(&idm.idm_global_mutex); + rw_destroy(&idm.idm_taskid_table_lock); + return (ENOMEM); + } + + /* start watchdog thread */ + idm.idm_wd_thread = thread_create(NULL, 0, + idm_wd_thread, NULL, 0, &p0, TS_RUN, minclsyspri); + if (idm.idm_wd_thread == NULL) { + /* Couldn't create the watchdog thread */ + taskq_destroy(idm.idm_global_taskq); + cv_destroy(&idm.idm_wd_cv); + cv_destroy(&idm.idm_tgt_svc_cv); + mutex_destroy(&idm.idm_global_mutex); + rw_destroy(&idm.idm_taskid_table_lock); + return (ENOMEM); + } + + mutex_enter(&idm.idm_global_mutex); + while (!idm.idm_wd_thread_running) + cv_wait(&idm.idm_wd_cv, &idm.idm_global_mutex); + mutex_exit(&idm.idm_global_mutex); + + /* + * Allocate the task ID table and set "next" to 0. + */ + + idm.idm_taskid_max = idm_max_taskids; + idm.idm_taskid_table = (idm_task_t **) + kmem_zalloc(idm.idm_taskid_max * sizeof (idm_task_t *), KM_SLEEP); + idm.idm_taskid_next = 0; + + /* Create the global buffer and task kmem caches */ + idm.idm_buf_cache = kmem_cache_create("idm_buf_cache", + sizeof (idm_buf_t), 8, NULL, NULL, NULL, NULL, NULL, KM_SLEEP); + + /* + * Note, we're explicitly allocating an additional iSER header- + * sized chunk for each of these elements. See idm_task_constructor(). + */ + idm.idm_task_cache = kmem_cache_create("idm_task_cache", + sizeof (idm_task_t) + IDM_TRANSPORT_HEADER_LENGTH, 8, + &idm_task_constructor, &idm_task_destructor, + NULL, NULL, NULL, KM_SLEEP); + + /* Create the service and connection context lists */ + list_create(&idm.idm_tgt_svc_list, sizeof (idm_svc_t), + offsetof(idm_svc_t, is_list_node)); + list_create(&idm.idm_tgt_conn_list, sizeof (idm_conn_t), + offsetof(idm_conn_t, ic_list_node)); + list_create(&idm.idm_ini_conn_list, sizeof (idm_conn_t), + offsetof(idm_conn_t, ic_list_node)); + + /* Initialize the native sockets transport */ + idm_so_init(&idm_transport_list[IDM_TRANSPORT_TYPE_SOCKETS]); + + /* Create connection ID pool */ + (void) idm_idpool_create(&idm.idm_conn_id_pool); + + return (DDI_SUCCESS); +} + +static int +_idm_fini(void) +{ + if (!list_is_empty(&idm.idm_ini_conn_list) || + !list_is_empty(&idm.idm_tgt_conn_list) || + !list_is_empty(&idm.idm_tgt_svc_list)) { + return (EBUSY); + } + + mutex_enter(&idm.idm_global_mutex); + idm.idm_wd_thread_running = B_FALSE; + cv_signal(&idm.idm_wd_cv); + mutex_exit(&idm.idm_global_mutex); + + thread_join(idm.idm_wd_thread_did); + + idm_idpool_destroy(&idm.idm_conn_id_pool); + idm_so_fini(); + list_destroy(&idm.idm_ini_conn_list); + list_destroy(&idm.idm_tgt_conn_list); + list_destroy(&idm.idm_tgt_svc_list); + kmem_cache_destroy(idm.idm_task_cache); + kmem_cache_destroy(idm.idm_buf_cache); + kmem_free(idm.idm_taskid_table, + idm.idm_taskid_max * sizeof (idm_task_t *)); + mutex_destroy(&idm.idm_global_mutex); + cv_destroy(&idm.idm_wd_cv); + cv_destroy(&idm.idm_tgt_svc_cv); + rw_destroy(&idm.idm_taskid_table_lock); + + return (0); +} diff --git a/usr/src/uts/common/io/idm/idm_conn_sm.c b/usr/src/uts/common/io/idm/idm_conn_sm.c new file mode 100644 index 000000000000..a46491c0382b --- /dev/null +++ b/usr/src/uts/common/io/idm/idm_conn_sm.c @@ -0,0 +1,1480 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define IDM_CONN_SM_STRINGS +#include + +boolean_t idm_sm_logging = B_FALSE; + +extern idm_global_t idm; /* Global state */ + +static void +idm_conn_event_locked(idm_conn_t *ic, idm_conn_event_t event, + uintptr_t event_info, idm_pdu_event_type_t pdu_event_type); + +static void +idm_conn_event_handler(void *event_ctx_opaque); + +static void +idm_state_s1_free(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_state_s2_xpt_wait(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_state_s3_xpt_up(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_state_s4_in_login(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_state_s5_logged_in(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_state_s6_in_logout(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_logout_req_timeout(void *arg); + +static void +idm_state_s7_logout_req(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_state_s8_cleanup(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_state_s9_init_error(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_state_s10_in_cleanup(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_state_s11_complete(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_state_s12_enable_dm(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_update_state(idm_conn_t *ic, idm_conn_state_t new_state, + idm_conn_event_ctx_t *event_ctx); + +static void +idm_conn_unref(void *ic_void); + +static idm_pdu_event_action_t +idm_conn_sm_validate_pdu(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx, + idm_pdu_t *pdu); + +static idm_status_t +idm_ffp_enable(idm_conn_t *ic); + +static void +idm_ffp_disable(idm_conn_t *ic, idm_ffp_disable_t disable_type); + +static void +idm_initial_login_actions(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +static void +idm_login_success_actions(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx); + +idm_status_t +idm_conn_sm_init(idm_conn_t *ic) +{ + char taskq_name[32]; + + /* + * Caller should have assigned a unique connection ID. Use this + * connection ID to create a unique connection name string + */ + ASSERT(ic->ic_internal_cid != 0); + (void) snprintf(taskq_name, sizeof (taskq_name) - 1, "conn_sm%08x", + ic->ic_internal_cid); + + ic->ic_state_taskq = taskq_create(taskq_name, 1, minclsyspri, 2, 2, + TASKQ_PREPOPULATE); + if (ic->ic_state_taskq == NULL) { + return (IDM_STATUS_FAIL); + } + + idm_sm_audit_init(&ic->ic_state_audit); + mutex_init(&ic->ic_state_mutex, NULL, MUTEX_DEFAULT, NULL); + cv_init(&ic->ic_state_cv, NULL, CV_DEFAULT, NULL); + + ic->ic_state = CS_S1_FREE; + ic->ic_last_state = CS_S1_FREE; + + return (IDM_STATUS_SUCCESS); +} + +void +idm_conn_sm_fini(idm_conn_t *ic) +{ + taskq_destroy(ic->ic_state_taskq); + + cv_destroy(&ic->ic_state_cv); + /* + * The thread that generated the event that got us here may still + * hold the ic_state_mutex. Once it is released we can safely + * destroy it since there is no way to locate the object now. + */ + mutex_enter(&ic->ic_state_mutex); + mutex_destroy(&ic->ic_state_mutex); +} + +void +idm_conn_event(idm_conn_t *ic, idm_conn_event_t event, uintptr_t event_info) +{ + mutex_enter(&ic->ic_state_mutex); + idm_conn_event_locked(ic, event, event_info, CT_NONE); + mutex_exit(&ic->ic_state_mutex); +} + +idm_status_t +idm_conn_reinstate_event(idm_conn_t *old_ic, idm_conn_t *new_ic) +{ + int result; + + mutex_enter(&old_ic->ic_state_mutex); + if (((old_ic->ic_conn_type == CONN_TYPE_INI) && + (old_ic->ic_state != CS_S8_CLEANUP)) || + ((old_ic->ic_conn_type == CONN_TYPE_TGT) && + (old_ic->ic_state < CS_S5_LOGGED_IN))) { + result = IDM_STATUS_FAIL; + } else { + result = IDM_STATUS_SUCCESS; + new_ic->ic_reinstate_conn = old_ic; + idm_conn_event_locked(new_ic->ic_reinstate_conn, + CE_CONN_REINSTATE, (uintptr_t)new_ic, CT_NONE); + } + mutex_exit(&old_ic->ic_state_mutex); + + return (result); +} + +void +idm_conn_tx_pdu_event(idm_conn_t *ic, idm_conn_event_t event, + uintptr_t event_info) +{ + ASSERT(mutex_owned(&ic->ic_state_mutex)); + ic->ic_pdu_events++; + idm_conn_event_locked(ic, event, event_info, CT_TX_PDU); +} + +void +idm_conn_rx_pdu_event(idm_conn_t *ic, idm_conn_event_t event, + uintptr_t event_info) +{ + ASSERT(mutex_owned(&ic->ic_state_mutex)); + ic->ic_pdu_events++; + idm_conn_event_locked(ic, event, event_info, CT_RX_PDU); +} + +static void +idm_conn_event_locked(idm_conn_t *ic, idm_conn_event_t event, + uintptr_t event_info, idm_pdu_event_type_t pdu_event_type) +{ + idm_conn_event_ctx_t *event_ctx; + + idm_sm_audit_event(&ic->ic_state_audit, SAS_IDM_CONN, + (int)ic->ic_state, (int)event, event_info); + + /* + * It's very difficult to prevent a few straggling events + * at the end. For example idm_sorx_thread will generate + * a CE_TRANSPORT_FAIL event when it exits. Rather than + * push complicated restrictions all over the code to + * prevent this we will simply drop the events (and in + * the case of PDU events release them appropriately) + * since they are irrelevant once we are in a terminal state. + * Of course those threads need to have appropriate holds on + * the connection otherwise it might disappear. + */ + if ((ic->ic_state == CS_S9_INIT_ERROR) || + (ic->ic_state == CS_S11_COMPLETE)) { + if ((pdu_event_type == CT_TX_PDU) || + (pdu_event_type == CT_RX_PDU)) { + ic->ic_pdu_events--; + idm_pdu_complete((idm_pdu_t *)event_info, + IDM_STATUS_SUCCESS); + } + IDM_SM_LOG(CE_NOTE, "*** Dropping event %s (%d) because of" + "state %s (%d)", + idm_ce_name[event], event, + idm_cs_name[ic->ic_state], ic->ic_state); + return; + } + + /* + * Normal event handling + */ + idm_conn_hold(ic); + + event_ctx = kmem_zalloc(sizeof (*event_ctx), KM_SLEEP); + event_ctx->iec_ic = ic; + event_ctx->iec_event = event; + event_ctx->iec_info = event_info; + event_ctx->iec_pdu_event_type = pdu_event_type; + + (void) taskq_dispatch(ic->ic_state_taskq, &idm_conn_event_handler, + event_ctx, TQ_SLEEP); +} + +static void +idm_conn_event_handler(void *event_ctx_opaque) +{ + idm_conn_event_ctx_t *event_ctx = event_ctx_opaque; + idm_conn_t *ic = event_ctx->iec_ic; + idm_pdu_t *pdu = (idm_pdu_t *)event_ctx->iec_info; + idm_pdu_event_action_t action; + + IDM_SM_LOG(CE_NOTE, "idm_conn_event_handler: conn %p event %s(%d)", + (void *)ic, idm_ce_name[event_ctx->iec_event], + event_ctx->iec_event); + DTRACE_PROBE2(conn__event, + idm_conn_t *, ic, smb_event_ctx_t *, event_ctx); + + /* + * Validate event + */ + ASSERT(event_ctx->iec_event != CE_UNDEFINED); + ASSERT3U(event_ctx->iec_event, <, CE_MAX_EVENT); + + /* + * Validate current state + */ + ASSERT(ic->ic_state != CS_S0_UNDEFINED); + ASSERT3U(ic->ic_state, <, CS_MAX_STATE); + + /* + * Validate PDU-related events against the current state. If a PDU + * is not allowed in the current state we change the event to a + * protocol error. This simplifies the state-specific event handlers. + * For example the CS_S2_XPT_WAIT state only needs to handle the + * CE_TX_PROTOCOL_ERROR and CE_RX_PROTOCOL_ERROR events since + * no PDU's can be transmitted or received in that state. + */ + if (event_ctx->iec_pdu_event_type != CT_NONE) { + ASSERT(pdu != NULL); + action = idm_conn_sm_validate_pdu(ic, event_ctx, pdu); + + switch (action) { + case CA_TX_PROTOCOL_ERROR: + /* + * Change event and forward the PDU + */ + event_ctx->iec_event = CE_TX_PROTOCOL_ERROR; + break; + case CA_RX_PROTOCOL_ERROR: + /* + * Change event and forward the PDU. + */ + event_ctx->iec_event = CE_RX_PROTOCOL_ERROR; + break; + case CA_FORWARD: + /* + * Let the state-specific event handlers take + * care of it. + */ + break; + case CA_DROP: + /* + * It never even happened + */ + IDM_SM_LOG(CE_NOTE, "*** drop PDU %p", (void *) pdu); + idm_pdu_complete(pdu, IDM_STATUS_FAIL); + break; + default: + ASSERT(0); + break; + } + } + + switch (ic->ic_state) { + case CS_S1_FREE: + idm_state_s1_free(ic, event_ctx); + break; + case CS_S2_XPT_WAIT: + idm_state_s2_xpt_wait(ic, event_ctx); + break; + case CS_S3_XPT_UP: + idm_state_s3_xpt_up(ic, event_ctx); + break; + case CS_S4_IN_LOGIN: + idm_state_s4_in_login(ic, event_ctx); + break; + case CS_S5_LOGGED_IN: + idm_state_s5_logged_in(ic, event_ctx); + break; + case CS_S6_IN_LOGOUT: + idm_state_s6_in_logout(ic, event_ctx); + break; + case CS_S7_LOGOUT_REQ: + idm_state_s7_logout_req(ic, event_ctx); + break; + case CS_S8_CLEANUP: + idm_state_s8_cleanup(ic, event_ctx); + break; + case CS_S9_INIT_ERROR: + idm_state_s9_init_error(ic, event_ctx); + break; + case CS_S10_IN_CLEANUP: + idm_state_s10_in_cleanup(ic, event_ctx); + break; + case CS_S11_COMPLETE: + idm_state_s11_complete(ic, event_ctx); + break; + case CS_S12_ENABLE_DM: + idm_state_s12_enable_dm(ic, event_ctx); + break; + default: + ASSERT(0); + break; + } + + /* + * Now that we've updated the state machine, if this was + * a PDU-related event take the appropriate action on the PDU + * (transmit it, forward it to the clients RX callback, drop + * it, etc). + */ + if (event_ctx->iec_pdu_event_type != CT_NONE) { + switch (action) { + case CA_TX_PROTOCOL_ERROR: + idm_pdu_tx_protocol_error(ic, pdu); + break; + case CA_RX_PROTOCOL_ERROR: + idm_pdu_rx_protocol_error(ic, pdu); + break; + case CA_FORWARD: + if (event_ctx->iec_pdu_event_type == CT_RX_PDU) { + idm_pdu_rx_forward(ic, pdu); + } else { + idm_pdu_tx_forward(ic, pdu); + } + break; + default: + ASSERT(0); + break; + } + } + + /* + * Update outstanding PDU event count (see idm_pdu_tx for + * how this is used) + */ + if ((event_ctx->iec_pdu_event_type == CT_TX_PDU) || + (event_ctx->iec_pdu_event_type == CT_RX_PDU)) { + mutex_enter(&ic->ic_state_mutex); + ic->ic_pdu_events--; + mutex_exit(&ic->ic_state_mutex); + } + + idm_conn_rele(ic); + kmem_free(event_ctx, sizeof (*event_ctx)); +} + +static void +idm_state_s1_free(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + switch (event_ctx->iec_event) { + case CE_CONNECT_REQ: + /* T1 */ + idm_update_state(ic, CS_S2_XPT_WAIT, event_ctx); + break; + case CE_CONNECT_ACCEPT: + /* T3 */ + idm_update_state(ic, CS_S3_XPT_UP, event_ctx); + break; + case CE_TX_PROTOCOL_ERROR: + case CE_RX_PROTOCOL_ERROR: + /* This should never happen */ + idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx); + break; + default: + ASSERT(0); + /*NOTREACHED*/ + } +} + + +static void +idm_state_s2_xpt_wait(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + switch (event_ctx->iec_event) { + case CE_CONNECT_SUCCESS: + /* T4 */ + idm_update_state(ic, CS_S4_IN_LOGIN, event_ctx); + break; + case CE_CONNECT_FAIL: + case CE_LOGOUT_OTHER_CONN_RCV: + case CE_TX_PROTOCOL_ERROR: + case CE_RX_PROTOCOL_ERROR: + /* T2 */ + idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx); + break; + default: + ASSERT(0); + /*NOTREACHED*/ + } +} + + +static void +idm_login_timeout(void *arg) +{ + idm_conn_t *ic = arg; + + idm_conn_event(ic, CE_LOGIN_TIMEOUT, NULL); +} + +static void +idm_state_s3_xpt_up(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + switch (event_ctx->iec_event) { + case CE_LOGIN_RCV: + /* T4 */ + idm_initial_login_actions(ic, event_ctx); + idm_update_state(ic, CS_S4_IN_LOGIN, event_ctx); + break; + case CE_LOGIN_TIMEOUT: + /* + * Don't need to cancel login timer since the timer is + * presumed to be the source of this event. + */ + (void) idm_notify_client(ic, CN_LOGIN_FAIL, NULL); + idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx); + break; + case CE_CONNECT_REJECT: + case CE_CONNECT_FAIL: + case CE_TRANSPORT_FAIL: + case CE_LOGOUT_OTHER_CONN_SND: + /* T6 */ + (void) untimeout(ic->ic_state_timeout); + (void) idm_notify_client(ic, CN_LOGIN_FAIL, NULL); + idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx); + break; + case CE_TX_PROTOCOL_ERROR: + case CE_RX_PROTOCOL_ERROR: + /* Don't care */ + break; + default: + ASSERT(0); + /*NOTREACHED*/ + } +} + +static void +idm_state_s4_in_login_fail_snd_done(idm_pdu_t *pdu, idm_status_t status) +{ + idm_conn_t *ic = pdu->isp_ic; + + /* + * This pdu callback can be invoked by the tx thread, + * so run the disconnect code from another thread. + */ + pdu->isp_status = status; + idm_conn_event(ic, CE_LOGIN_FAIL_SND_DONE, (uintptr_t)pdu); +} + +static void +idm_state_s4_in_login(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + idm_pdu_t *pdu; + + /* + * Login timer should no longer be active after leaving this + * state. + */ + switch (event_ctx->iec_event) { + case CE_LOGIN_SUCCESS_RCV: + case CE_LOGIN_SUCCESS_SND: + (void) untimeout(ic->ic_state_timeout); + idm_login_success_actions(ic, event_ctx); + if (ic->ic_rdma_extensions) { + /* T19 */ + idm_update_state(ic, CS_S12_ENABLE_DM, event_ctx); + } else { + /* T5 */ + idm_update_state(ic, CS_S5_LOGGED_IN, event_ctx); + } + break; + case CE_LOGIN_TIMEOUT: + /* T7 */ + (void) idm_notify_client(ic, CN_LOGIN_FAIL, NULL); + idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx); + break; + case CE_LOGIN_FAIL_SND_DONE: + (void) idm_notify_client(ic, CN_LOGIN_FAIL, NULL); + idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx); + break; + case CE_LOGIN_FAIL_SND: + /* + * Allow the logout response pdu to be sent and defer + * the state machine update until the completion callback. + * Only 1 level or callback interposition is allowed. + */ + (void) untimeout(ic->ic_state_timeout); + pdu = (idm_pdu_t *)event_ctx->iec_info; + ASSERT(ic->ic_client_callback == NULL); + ic->ic_client_callback = pdu->isp_callback; + pdu->isp_callback = + idm_state_s4_in_login_fail_snd_done; + break; + case CE_LOGIN_FAIL_RCV: + case CE_TRANSPORT_FAIL: + case CE_LOGOUT_OTHER_CONN_SND: + case CE_LOGOUT_OTHER_CONN_RCV: + /* T7 */ + (void) untimeout(ic->ic_state_timeout); + (void) idm_notify_client(ic, CN_LOGIN_FAIL, NULL); + idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx); + break; + case CE_LOGIN_SND: + /* + * Initiator connections will see initial login PDU + * in this state. Target connections see initial + * login PDU in "xpt up" state. + */ + mutex_enter(&ic->ic_state_mutex); + if (!(ic->ic_state_flags & CF_INITIAL_LOGIN)) { + idm_initial_login_actions(ic, event_ctx); + } + mutex_exit(&ic->ic_state_mutex); + break; + case CE_MISC_TX: + case CE_MISC_RX: + case CE_LOGIN_RCV: + case CE_TX_PROTOCOL_ERROR: + case CE_RX_PROTOCOL_ERROR: + /* Don't care */ + break; + default: + ASSERT(0); + /*NOTREACHED*/ + } +} + + +static void +idm_state_s5_logged_in(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + switch (event_ctx->iec_event) { + case CE_LOGOUT_THIS_CONN_RCV: + case CE_LOGOUT_THIS_CONN_SND: + case CE_LOGOUT_OTHER_CONN_RCV: + case CE_LOGOUT_OTHER_CONN_SND: + /* T9 */ + idm_ffp_disable(ic, FD_CONN_LOGOUT); /* Explicit logout */ + idm_update_state(ic, CS_S6_IN_LOGOUT, event_ctx); + break; + case CE_LOGOUT_SESSION_RCV: + case CE_LOGOUT_SESSION_SND: + /* T9 */ + idm_ffp_disable(ic, FD_SESS_LOGOUT); /* Explicit logout */ + idm_update_state(ic, CS_S6_IN_LOGOUT, event_ctx); + break; + case CE_LOGOUT_SESSION_SUCCESS: + /* T8 */ + idm_ffp_disable(ic, FD_SESS_LOGOUT); /* Explicit logout */ + + /* Close connection */ + if (IDM_CONN_ISTGT(ic)) { + ic->ic_transport_ops->it_tgt_conn_disconnect(ic); + } else { + ic->ic_transport_ops->it_ini_conn_disconnect(ic); + } + + idm_update_state(ic, CS_S11_COMPLETE, event_ctx); + break; + case CE_ASYNC_LOGOUT_RCV: + case CE_ASYNC_LOGOUT_SND: + /* T11 */ + idm_update_state(ic, CS_S7_LOGOUT_REQ, event_ctx); + break; + case CE_TRANSPORT_FAIL: + case CE_ASYNC_DROP_CONN_RCV: + case CE_ASYNC_DROP_CONN_SND: + case CE_ASYNC_DROP_ALL_CONN_RCV: + case CE_ASYNC_DROP_ALL_CONN_SND: + /* T15 */ + idm_ffp_disable(ic, FD_CONN_FAIL); /* Implicit logout */ + idm_update_state(ic, CS_S8_CLEANUP, event_ctx); + break; + case CE_MISC_TX: + case CE_MISC_RX: + case CE_TX_PROTOCOL_ERROR: + case CE_RX_PROTOCOL_ERROR: + /* Don't care */ + break; + default: + ASSERT(0); + } +} + +static void +idm_state_s6_in_logout_success_snd_done(idm_pdu_t *pdu, idm_status_t status) +{ + idm_conn_t *ic = pdu->isp_ic; + + /* + * This pdu callback can be invoked by the tx thread, + * so run the disconnect code from another thread. + */ + pdu->isp_status = status; + idm_conn_event(ic, CE_LOGOUT_SUCCESS_SND_DONE, (uintptr_t)pdu); +} + +static void +idm_state_s6_in_logout_fail_snd_done(idm_pdu_t *pdu, idm_status_t status) +{ + idm_conn_t *ic = pdu->isp_ic; + + /* + * This pdu callback can be invoked by the tx thread, + * so run the disconnect code from another thread. + */ + pdu->isp_status = status; + idm_conn_event(ic, CE_LOGOUT_FAIL_SND_DONE, (uintptr_t)pdu); +} + +static void +idm_state_s6_in_logout(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + idm_pdu_t *pdu; + + switch (event_ctx->iec_event) { + case CE_LOGOUT_SUCCESS_SND_DONE: + pdu = (idm_pdu_t *)event_ctx->iec_info; + + /* Close connection (if it's not already closed) */ + ASSERT(IDM_CONN_ISTGT(ic)); + ic->ic_transport_ops->it_tgt_conn_disconnect(ic); + + /* restore client callback */ + pdu->isp_callback = ic->ic_client_callback; + ic->ic_client_callback = NULL; + idm_pdu_complete(pdu, pdu->isp_status); + idm_update_state(ic, CS_S11_COMPLETE, event_ctx); + break; + case CE_LOGOUT_FAIL_SND_DONE: + pdu = (idm_pdu_t *)event_ctx->iec_info; + /* restore client callback */ + pdu->isp_callback = ic->ic_client_callback; + ic->ic_client_callback = NULL; + idm_pdu_complete(pdu, pdu->isp_status); + idm_update_state(ic, CS_S8_CLEANUP, event_ctx); + break; + case CE_LOGOUT_SUCCESS_SND: + case CE_LOGOUT_FAIL_SND: + /* + * Allow the logout response pdu to be sent and defer + * the state machine update until the completion callback. + * Only 1 level or callback interposition is allowed. + */ + pdu = (idm_pdu_t *)event_ctx->iec_info; + ASSERT(ic->ic_client_callback == NULL); + ic->ic_client_callback = pdu->isp_callback; + if (event_ctx->iec_event == CE_LOGOUT_SUCCESS_SND) { + pdu->isp_callback = + idm_state_s6_in_logout_success_snd_done; + } else { + pdu->isp_callback = + idm_state_s6_in_logout_fail_snd_done; + } + break; + case CE_LOGOUT_SUCCESS_RCV: + case CE_LOGOUT_SESSION_SUCCESS: + /* T13 */ + + /* Close connection (if it's not already closed) */ + if (IDM_CONN_ISTGT(ic)) { + ic->ic_transport_ops->it_tgt_conn_disconnect(ic); + } else { + ic->ic_transport_ops->it_ini_conn_disconnect(ic); + } + + idm_update_state(ic, CS_S11_COMPLETE, event_ctx); + break; + case CE_ASYNC_LOGOUT_RCV: + /* T14 Do nothing */ + break; + case CE_TRANSPORT_FAIL: + case CE_ASYNC_DROP_CONN_RCV: + case CE_ASYNC_DROP_CONN_SND: + case CE_ASYNC_DROP_ALL_CONN_RCV: + case CE_ASYNC_DROP_ALL_CONN_SND: + case CE_LOGOUT_FAIL_RCV: + idm_update_state(ic, CS_S8_CLEANUP, event_ctx); + break; + case CE_TX_PROTOCOL_ERROR: + case CE_RX_PROTOCOL_ERROR: + case CE_MISC_TX: + case CE_MISC_RX: + /* Don't care */ + break; + default: + ASSERT(0); + } +} + + +static void +idm_logout_req_timeout(void *arg) +{ + idm_conn_t *ic = arg; + + idm_conn_event(ic, CE_LOGOUT_TIMEOUT, NULL); +} + +static void +idm_state_s7_logout_req(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + /* Must cancel logout timer before leaving this state */ + switch (event_ctx->iec_event) { + case CE_LOGOUT_THIS_CONN_RCV: + case CE_LOGOUT_THIS_CONN_SND: + case CE_LOGOUT_OTHER_CONN_RCV: + case CE_LOGOUT_OTHER_CONN_SND: + /* T10 */ + if (IDM_CONN_ISTGT(ic)) { + (void) untimeout(ic->ic_state_timeout); + } + idm_ffp_disable(ic, FD_CONN_LOGOUT); /* Explicit logout */ + idm_update_state(ic, CS_S6_IN_LOGOUT, event_ctx); + break; + case CE_LOGOUT_SESSION_RCV: + case CE_LOGOUT_SESSION_SND: + /* T10 */ + if (IDM_CONN_ISTGT(ic)) { + (void) untimeout(ic->ic_state_timeout); + } + idm_ffp_disable(ic, FD_SESS_LOGOUT); /* Explicit logout */ + idm_update_state(ic, CS_S6_IN_LOGOUT, event_ctx); + break; + case CE_ASYNC_LOGOUT_RCV: + case CE_ASYNC_LOGOUT_SND: + /* T12 Do nothing */ + break; + case CE_TRANSPORT_FAIL: + case CE_ASYNC_DROP_CONN_RCV: + case CE_ASYNC_DROP_CONN_SND: + case CE_ASYNC_DROP_ALL_CONN_RCV: + case CE_ASYNC_DROP_ALL_CONN_SND: + /* T16 */ + if (IDM_CONN_ISTGT(ic)) { + (void) untimeout(ic->ic_state_timeout); + } + /* FALLTHROUGH */ + case CE_LOGOUT_TIMEOUT: + idm_ffp_disable(ic, FD_CONN_FAIL); /* Implicit logout */ + idm_update_state(ic, CS_S8_CLEANUP, event_ctx); + break; + case CE_LOGOUT_SESSION_SUCCESS: + /* T18 */ + if (IDM_CONN_ISTGT(ic)) { + (void) untimeout(ic->ic_state_timeout); + } + idm_ffp_disable(ic, FD_SESS_LOGOUT); /* Explicit logout */ + + /* Close connection (if it's not already closed) */ + if (IDM_CONN_ISTGT(ic)) { + ic->ic_transport_ops->it_tgt_conn_disconnect(ic); + } else { + ic->ic_transport_ops->it_ini_conn_disconnect(ic); + } + + idm_update_state(ic, CS_S11_COMPLETE, event_ctx); + break; + case CE_TX_PROTOCOL_ERROR: + case CE_RX_PROTOCOL_ERROR: + case CE_MISC_TX: + case CE_MISC_RX: + /* Don't care */ + break; + default: + ASSERT(0); + } +} + + +static void +idm_cleanup_timeout(void *arg) +{ + idm_conn_t *ic = arg; + + idm_conn_event(ic, CE_CLEANUP_TIMEOUT, NULL); +} + +static void +idm_state_s8_cleanup(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + idm_pdu_t *pdu; + + /* + * Need to cancel the cleanup timeout before leaving this state + * if it hasn't already fired. + */ + switch (event_ctx->iec_event) { + case CE_LOGOUT_SUCCESS_RCV: + case CE_LOGOUT_SUCCESS_SND: + case CE_LOGOUT_SESSION_SUCCESS: + (void) untimeout(ic->ic_state_timeout); + /*FALLTHROUGH*/ + case CE_CLEANUP_TIMEOUT: + /* M1 */ + idm_update_state(ic, CS_S11_COMPLETE, event_ctx); + break; + case CE_LOGOUT_OTHER_CONN_RCV: + case CE_LOGOUT_OTHER_CONN_SND: + /* M2 */ + idm_update_state(ic, CS_S10_IN_CLEANUP, event_ctx); + break; + case CE_LOGOUT_SUCCESS_SND_DONE: + case CE_LOGOUT_FAIL_SND_DONE: + pdu = (idm_pdu_t *)event_ctx->iec_info; + /* restore client callback */ + pdu->isp_callback = ic->ic_client_callback; + ic->ic_client_callback = NULL; + idm_pdu_complete(pdu, pdu->isp_status); + break; + case CE_LOGOUT_SESSION_RCV: + case CE_LOGOUT_SESSION_SND: + case CE_TX_PROTOCOL_ERROR: + case CE_RX_PROTOCOL_ERROR: + case CE_MISC_TX: + case CE_MISC_RX: + case CE_TRANSPORT_FAIL: + case CE_LOGOUT_TIMEOUT: + /* Don't care */ + break; + default: + ASSERT(0); + } +} + +/* ARGSUSED */ +static void +idm_state_s9_init_error(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + if (ic->ic_conn_type == CONN_TYPE_INI) { + mutex_enter(&ic->ic_state_mutex); + ic->ic_state_flags |= CF_ERROR; + ic->ic_conn_sm_status = IDM_STATUS_FAIL; + cv_signal(&ic->ic_state_cv); + mutex_exit(&ic->ic_state_mutex); + } +} + +static void +idm_state_s10_in_cleanup(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + idm_pdu_t *pdu; + + /* + * Need to cancel the cleanup timeout before leaving this state + * if it hasn't already fired. + */ + switch (event_ctx->iec_event) { + case CE_LOGOUT_FAIL_RCV: + case CE_LOGOUT_FAIL_SND: + idm_update_state(ic, CS_S8_CLEANUP, event_ctx); + break; + case CE_LOGOUT_SUCCESS_SND: + case CE_LOGOUT_SUCCESS_RCV: + case CE_LOGOUT_SESSION_SUCCESS: + (void) untimeout(ic->ic_state_timeout); + /*FALLTHROUGH*/ + case CE_CLEANUP_TIMEOUT: + idm_update_state(ic, CS_S11_COMPLETE, event_ctx); + break; + case CE_LOGOUT_SUCCESS_SND_DONE: + case CE_LOGOUT_FAIL_SND_DONE: + pdu = (idm_pdu_t *)event_ctx->iec_info; + /* restore client callback */ + pdu->isp_callback = ic->ic_client_callback; + ic->ic_client_callback = NULL; + idm_pdu_complete(pdu, pdu->isp_status); + break; + case CE_TX_PROTOCOL_ERROR: + case CE_RX_PROTOCOL_ERROR: + case CE_MISC_TX: + case CE_MISC_RX: + case CE_LOGOUT_TIMEOUT: + /* Don't care */ + break; + default: + ASSERT(0); + } +} + +/* ARGSUSED */ +static void +idm_state_s11_complete(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + idm_pdu_t *pdu; + + /* + * Cleanup logout success/fail completion if it's been delayed + * until now. + */ + switch (event_ctx->iec_event) { + case CE_LOGOUT_SUCCESS_SND_DONE: + case CE_LOGOUT_FAIL_SND_DONE: + pdu = (idm_pdu_t *)event_ctx->iec_info; + /* restore client callback */ + pdu->isp_callback = ic->ic_client_callback; + ic->ic_client_callback = NULL; + idm_pdu_complete(pdu, pdu->isp_status); + break; + } +} + +static void +idm_state_s12_enable_dm(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + switch (event_ctx->iec_event) { + case CE_ENABLE_DM_SUCCESS: + /* T20 */ + idm_update_state(ic, CS_S5_LOGGED_IN, event_ctx); + break; + case CE_ENABLE_DM_FAIL: + /* T21 */ + idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx); + break; + case CE_TRANSPORT_FAIL: + /* + * We expect to always hear back from the transport layer + * once we have an "enable data-mover" request outstanding. + * Therefore we'll ignore other events that may occur even + * when they clearly indicate a problem and wait for + * CE_ENABLE_DM_FAIL. On a related note this means the + * transport must ensure that it eventually completes the + * "enable data-mover" operation with either success or + * failure -- otherwise we'll be stuck here. + */ + break; + default: + ASSERT(0); + break; + } +} + +static void +idm_update_state(idm_conn_t *ic, idm_conn_state_t new_state, + idm_conn_event_ctx_t *event_ctx) +{ + int rc; + idm_status_t idm_status; + + /* + * Validate new state + */ + ASSERT(new_state != CS_S0_UNDEFINED); + ASSERT3U(new_state, <, CS_MAX_STATE); + + /* + * Update state in context. We protect this with a mutex + * even though the state machine code is single threaded so that + * other threads can check the state value atomically. + */ + new_state = (new_state < CS_MAX_STATE) ? + new_state : CS_S0_UNDEFINED; + + IDM_SM_LOG(CE_NOTE, "idm_update_state: conn %p, evt %s(%d), " + "%s(%d) --> %s(%d)", (void *)ic, + idm_ce_name[event_ctx->iec_event], event_ctx->iec_event, + idm_cs_name[ic->ic_state], ic->ic_state, + idm_cs_name[new_state], new_state); + + DTRACE_PROBE2(conn__state__change, + idm_conn_t *, ic, idm_conn_state_t, new_state); + + mutex_enter(&ic->ic_state_mutex); + idm_sm_audit_state_change(&ic->ic_state_audit, SAS_IDM_CONN, + (int)ic->ic_state, (int)new_state); + ic->ic_last_state = ic->ic_state; + ic->ic_state = new_state; + cv_signal(&ic->ic_state_cv); + mutex_exit(&ic->ic_state_mutex); + + switch (ic->ic_state) { + case CS_S1_FREE: + ASSERT(0); /* Initial state, can't return */ + break; + case CS_S2_XPT_WAIT: + if ((rc = idm_ini_conn_finish(ic)) != 0) { + idm_conn_event(ic, CE_CONNECT_FAIL, NULL); + } else { + idm_conn_event(ic, CE_CONNECT_SUCCESS, NULL); + } + break; + case CS_S3_XPT_UP: + /* + * Finish any connection related setup including + * waking up the idm_tgt_conn_accept thread. + * and starting the login timer. If the function + * fails then we return to "free" state. + */ + if ((rc = idm_tgt_conn_finish(ic)) != IDM_STATUS_SUCCESS) { + switch (rc) { + case IDM_STATUS_REJECT: + idm_conn_event(ic, CE_CONNECT_REJECT, NULL); + break; + default: + idm_conn_event(ic, CE_CONNECT_FAIL, NULL); + break; + } + } + + /* + * First login received will cause a transition to + * CS_S4_IN_LOGIN. Start login timer. + */ + ic->ic_state_timeout = timeout(idm_login_timeout, ic, + drv_usectohz(IDM_LOGIN_SECONDS*1000000)); + break; + case CS_S4_IN_LOGIN: + if (ic->ic_conn_type == CONN_TYPE_INI) { + mutex_enter(&ic->ic_state_mutex); + ic->ic_state_flags |= CF_LOGIN_READY; + cv_signal(&ic->ic_state_cv); + mutex_exit(&ic->ic_state_mutex); + } + break; + case CS_S5_LOGGED_IN: + ASSERT(!ic->ic_ffp); + /* + * IDM can go to FFP before the initiator but it + * needs to go to FFP after the target (IDM target should + * go to FFP after notify_ack). + */ + idm_status = idm_ffp_enable(ic); + if (idm_status != IDM_STATUS_SUCCESS) { + idm_conn_event(ic, CE_TRANSPORT_FAIL, NULL); + } + + if (ic->ic_reinstate_conn) { + /* Connection reinstatement is complete */ + idm_conn_event_locked(ic->ic_reinstate_conn, + CE_CONN_REINSTATE_SUCCESS, NULL, CT_NONE); + } + break; + case CS_S6_IN_LOGOUT: + break; + case CS_S7_LOGOUT_REQ: + /* Start logout timer for target connections */ + if (IDM_CONN_ISTGT(ic)) { + ic->ic_state_timeout = timeout(idm_logout_req_timeout, + ic, drv_usectohz(IDM_LOGOUT_SECONDS*1000000)); + } + break; + case CS_S8_CLEANUP: + /* Close connection (if it's not already closed) */ + if (IDM_CONN_ISTGT(ic)) { + ic->ic_transport_ops->it_tgt_conn_disconnect(ic); + } else { + ic->ic_transport_ops->it_ini_conn_disconnect(ic); + } + + /* Stop executing active tasks */ + idm_task_abort(ic, NULL, AT_INTERNAL_SUSPEND); + + /* Start logout timer */ + ic->ic_state_timeout = timeout(idm_cleanup_timeout, ic, + drv_usectohz(IDM_CLEANUP_SECONDS*1000000)); + break; + case CS_S10_IN_CLEANUP: + break; + case CS_S9_INIT_ERROR: + if (IDM_CONN_ISTGT(ic)) { + ic->ic_transport_ops->it_tgt_conn_disconnect(ic); + } else { + mutex_enter(&ic->ic_state_mutex); + ic->ic_state_flags |= CF_ERROR; + ic->ic_conn_sm_status = IDM_STATUS_FAIL; + cv_signal(&ic->ic_state_cv); + mutex_exit(&ic->ic_state_mutex); + ic->ic_transport_ops->it_ini_conn_disconnect(ic); + } + /*FALLTHROUGH*/ + case CS_S11_COMPLETE: + /* No more traffic on this connection */ + (void) idm_notify_client(ic, CN_CONNECT_LOST, NULL); + + /* Abort all tasks */ + idm_task_abort(ic, NULL, AT_INTERNAL_ABORT); + + /* + * Handle terminal state actions on the global taskq so + * we can clean up all the connection resources from + * a separate thread context. + */ + idm_refcnt_async_wait_ref(&ic->ic_refcnt, &idm_conn_unref); + break; + case CS_S12_ENABLE_DM: + + /* + * The Enable DM state indicates the initiator to initiate + * the hello sequence and the target to get ready to accept + * the iSER Hello Message. + */ + idm_status = (IDM_CONN_ISINI(ic)) ? + ic->ic_transport_ops->it_ini_enable_datamover(ic) : + ic->ic_transport_ops->it_tgt_enable_datamover(ic); + + if (idm_status == IDM_STATUS_SUCCESS) { + idm_conn_event(ic, CE_ENABLE_DM_SUCCESS, NULL); + } else { + idm_conn_event(ic, CE_ENABLE_DM_FAIL, NULL); + } + + break; + } +} + + +static void +idm_conn_unref(void *ic_void) +{ + idm_conn_t *ic = ic_void; + + /* + * Client should not be notified that the connection is destroyed + * until all references on the idm connection have been removed. + * Otherwise references on the associated client context would need + * to be tracked separately which seems like a waste (at least when + * there is a one for one correspondence with references on the + * IDM connection). + */ + if (IDM_CONN_ISTGT(ic)) { + (void) idm_notify_client(ic, CN_CONNECT_DESTROY, NULL); + idm_svc_conn_destroy(ic); + } else { + /* Initiator may destroy connection during this call */ + (void) idm_notify_client(ic, CN_CONNECT_DESTROY, NULL); + } +} + + +static idm_pdu_event_action_t +idm_conn_sm_validate_pdu(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx, + idm_pdu_t *pdu) +{ + char *reason_string; + idm_pdu_event_action_t action; + + ASSERT((event_ctx->iec_pdu_event_type == CT_RX_PDU) || + (event_ctx->iec_pdu_event_type == CT_TX_PDU)); + + /* + * Let's check the simple stuff first. Make sure if this is a + * target connection that the PDU is appropriate for a target + * and if this is an initiator connection that the PDU is + * appropriate for an initiator. This code is not in the data + * path so organization is more important than performance. + */ + switch (IDM_PDU_OPCODE(pdu)) { + case ISCSI_OP_NOOP_OUT: + case ISCSI_OP_SCSI_CMD: + case ISCSI_OP_SCSI_TASK_MGT_MSG: + case ISCSI_OP_LOGIN_CMD: + case ISCSI_OP_TEXT_CMD: + case ISCSI_OP_SCSI_DATA: + case ISCSI_OP_LOGOUT_CMD: + case ISCSI_OP_SNACK_CMD: + /* + * Only the initiator should send these PDU's and + * only the target should receive them. + */ + if (IDM_CONN_ISINI(ic) && + (event_ctx->iec_pdu_event_type == CT_RX_PDU)) { + reason_string = "Invalid RX PDU for initiator"; + action = CA_RX_PROTOCOL_ERROR; + goto validate_pdu_done; + } + + if (IDM_CONN_ISTGT(ic) && + (event_ctx->iec_pdu_event_type == CT_TX_PDU)) { + reason_string = "Invalid TX PDU for target"; + action = CA_TX_PROTOCOL_ERROR; + goto validate_pdu_done; + } + break; + case ISCSI_OP_NOOP_IN: + case ISCSI_OP_SCSI_RSP: + case ISCSI_OP_SCSI_TASK_MGT_RSP: + case ISCSI_OP_LOGIN_RSP: + case ISCSI_OP_TEXT_RSP: + case ISCSI_OP_SCSI_DATA_RSP: + case ISCSI_OP_LOGOUT_RSP: + case ISCSI_OP_RTT_RSP: + case ISCSI_OP_ASYNC_EVENT: + case ISCSI_OP_REJECT_MSG: + /* + * Only the target should send these PDU's and + * only the initiator should receive them. + */ + if (IDM_CONN_ISTGT(ic) && + (event_ctx->iec_pdu_event_type == CT_RX_PDU)) { + reason_string = "Invalid RX PDU for target"; + action = CA_RX_PROTOCOL_ERROR; + goto validate_pdu_done; + } + + if (IDM_CONN_ISINI(ic) && + (event_ctx->iec_pdu_event_type == CT_TX_PDU)) { + reason_string = "Invalid TX PDU for initiator"; + action = CA_TX_PROTOCOL_ERROR; + goto validate_pdu_done; + } + break; + default: + reason_string = "Unknown PDU Type"; + action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ? + CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR); + goto validate_pdu_done; + } + + /* + * Now validate the opcodes against the current state. + */ + reason_string = "PDU not allowed in current state"; + switch (IDM_PDU_OPCODE(pdu)) { + case ISCSI_OP_NOOP_OUT: + case ISCSI_OP_NOOP_IN: + /* + * Obviously S1-S3 are not allowed since login hasn't started. + * S8 is probably out as well since the connection has been + * dropped. + */ + switch (ic->ic_state) { + case CS_S4_IN_LOGIN: + case CS_S5_LOGGED_IN: + case CS_S6_IN_LOGOUT: + case CS_S7_LOGOUT_REQ: + action = CA_FORWARD; + goto validate_pdu_done; + case CS_S8_CLEANUP: + case CS_S10_IN_CLEANUP: + action = CA_DROP; + break; + default: + action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ? + CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR); + goto validate_pdu_done; + } + /*NOTREACHED*/ + case ISCSI_OP_SCSI_CMD: + case ISCSI_OP_SCSI_RSP: + case ISCSI_OP_SCSI_TASK_MGT_MSG: + case ISCSI_OP_SCSI_TASK_MGT_RSP: + case ISCSI_OP_SCSI_DATA: + case ISCSI_OP_SCSI_DATA_RSP: + case ISCSI_OP_RTT_RSP: + case ISCSI_OP_SNACK_CMD: + case ISCSI_OP_TEXT_CMD: + case ISCSI_OP_TEXT_RSP: + switch (ic->ic_state) { + case CS_S5_LOGGED_IN: + case CS_S6_IN_LOGOUT: + case CS_S7_LOGOUT_REQ: + action = CA_FORWARD; + goto validate_pdu_done; + case CS_S8_CLEANUP: + case CS_S10_IN_CLEANUP: + action = CA_DROP; + break; + default: + action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ? + CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR); + goto validate_pdu_done; + } + /*NOTREACHED*/ + case ISCSI_OP_LOGOUT_CMD: + case ISCSI_OP_LOGOUT_RSP: + case ISCSI_OP_REJECT_MSG: + case ISCSI_OP_ASYNC_EVENT: + switch (ic->ic_state) { + case CS_S5_LOGGED_IN: + case CS_S6_IN_LOGOUT: + case CS_S7_LOGOUT_REQ: + action = CA_FORWARD; + goto validate_pdu_done; + case CS_S8_CLEANUP: + case CS_S10_IN_CLEANUP: + action = CA_DROP; + break; + default: + action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ? + CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR); + goto validate_pdu_done; + } + /*NOTREACHED*/ + case ISCSI_OP_LOGIN_CMD: + case ISCSI_OP_LOGIN_RSP: + switch (ic->ic_state) { + case CS_S3_XPT_UP: + case CS_S4_IN_LOGIN: + action = CA_FORWARD; + goto validate_pdu_done; + default: + action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ? + CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR); + goto validate_pdu_done; + } + /*NOTREACHED*/ + default: + /* This should never happen -- we already checked above */ + ASSERT(0); + /*NOTREACHED*/ + } + + action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ? + CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR); + +validate_pdu_done: + if (action != CA_FORWARD) { + DTRACE_PROBE2(idm__int__protocol__error, + idm_conn_event_ctx_t *, event_ctx, + char *, reason_string); + } + + return (action); +} + +/* ARGSUSED */ +void +idm_pdu_tx_protocol_error(idm_conn_t *ic, idm_pdu_t *pdu) +{ + /* + * Return the PDU to the caller indicating it was a protocol error. + * Caller can take appropriate action. + */ + idm_pdu_complete(pdu, IDM_STATUS_PROTOCOL_ERROR); +} + +void +idm_pdu_rx_protocol_error(idm_conn_t *ic, idm_pdu_t *pdu) +{ + /* + * Forward PDU to caller indicating it is a protocol error. + * Caller should take appropriate action. + */ + (*ic->ic_conn_ops.icb_rx_error)(ic, pdu, IDM_STATUS_PROTOCOL_ERROR); +} + +idm_status_t +idm_notify_client(idm_conn_t *ic, idm_client_notify_t cn, uintptr_t data) +{ + /* + * We may want to make this more complicated at some point but + * for now lets just call the client's notify function and return + * the status. + */ + return ((*ic->ic_conn_ops.icb_client_notify)(ic, cn, data)); +} + +static idm_status_t +idm_ffp_enable(idm_conn_t *ic) +{ + idm_status_t rc; + + /* + * On the initiator side the client will see this notification + * before the actual login succes PDU. This shouldn't be a big + * deal since the initiator drives the connection. It can simply + * wait for the login response then start sending SCSI commands. + * Kind ugly though compared with the way things work on target + * connections. + */ + mutex_enter(&ic->ic_state_mutex); + ic->ic_ffp = B_TRUE; + mutex_exit(&ic->ic_state_mutex); + + rc = idm_notify_client(ic, CN_FFP_ENABLED, NULL); + if (rc != IDM_STATUS_SUCCESS) { + mutex_enter(&ic->ic_state_mutex); + ic->ic_ffp = B_FALSE; + mutex_exit(&ic->ic_state_mutex); + } + return (rc); +} + +static void +idm_ffp_disable(idm_conn_t *ic, idm_ffp_disable_t disable_type) +{ + mutex_enter(&ic->ic_state_mutex); + ic->ic_ffp = B_FALSE; + mutex_exit(&ic->ic_state_mutex); + + /* Client can't "fail" CN_FFP_DISABLED */ + (void) idm_notify_client(ic, CN_FFP_DISABLED, + (uintptr_t)disable_type); +} + +static void +idm_initial_login_actions(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + ASSERT((event_ctx->iec_event == CE_LOGIN_RCV) || + (event_ctx->iec_event == CE_LOGIN_SND)); + + /* + * Currently it's not clear what we would do here -- since + * we went to the trouble of coding an "initial login" hook + * we'll leave it in for now. Remove before integration if + * it's not used for anything. + */ + ic->ic_state_flags |= CF_INITIAL_LOGIN; +} + +static void +idm_login_success_actions(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx) +{ + idm_pdu_t *pdu = (idm_pdu_t *)event_ctx->iec_info; + iscsi_login_hdr_t *login_req = + (iscsi_login_hdr_t *)pdu->isp_hdr; + + ASSERT((event_ctx->iec_event == CE_LOGIN_SUCCESS_RCV) || + (event_ctx->iec_event == CE_LOGIN_SUCCESS_SND)); + + /* + * Save off CID + */ + mutex_enter(&ic->ic_state_mutex); + ic->ic_login_cid = ntohs(login_req->cid); + ic->ic_login_info_valid = B_TRUE; + + mutex_exit(&ic->ic_state_mutex); +} diff --git a/usr/src/uts/common/io/idm/idm_impl.c b/usr/src/uts/common/io/idm/idm_impl.c new file mode 100644 index 000000000000..c7f2d4704868 --- /dev/null +++ b/usr/src/uts/common/io/idm/idm_impl.c @@ -0,0 +1,1023 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +extern idm_transport_t idm_transport_list[]; + +void +idm_pdu_rx(idm_conn_t *ic, idm_pdu_t *pdu) +{ + iscsi_async_evt_hdr_t *async_evt; + + /* + * If we are in full-featured mode then route SCSI-related + * commands to the appropriate function vector + */ + ic->ic_timestamp = ddi_get_lbolt(); + mutex_enter(&ic->ic_state_mutex); + if (ic->ic_ffp && ic->ic_pdu_events == 0) { + mutex_exit(&ic->ic_state_mutex); + + if (idm_pdu_rx_forward_ffp(ic, pdu) == B_TRUE) { + /* Forwarded SCSI-related commands */ + return; + } + mutex_enter(&ic->ic_state_mutex); + } + + /* + * If we get here with a SCSI-related PDU then we are not in + * full-feature mode and the PDU is a protocol error (SCSI command + * PDU's may sometimes be an exception, see below). All + * non-SCSI PDU's get treated them the same regardless of whether + * we are in full-feature mode. + * + * Look at the opcode and in some cases the PDU status and + * determine the appropriate event to send to the connection + * state machine. Generate the event, passing the PDU as data. + * If the current connection state allows reception of the event + * the PDU will be submitted to the IDM client for processing, + * otherwise the PDU will be dropped. + */ + switch (IDM_PDU_OPCODE(pdu)) { + case ISCSI_OP_LOGIN_CMD: + idm_conn_rx_pdu_event(ic, CE_LOGIN_RCV, (uintptr_t)pdu); + break; + case ISCSI_OP_LOGIN_RSP: + idm_parse_login_rsp(ic, pdu, /* RX */ B_TRUE); + break; + case ISCSI_OP_LOGOUT_CMD: + idm_parse_logout_req(ic, pdu, /* RX */ B_TRUE); + break; + case ISCSI_OP_LOGOUT_RSP: + idm_parse_logout_rsp(ic, pdu, /* RX */ B_TRUE); + break; + case ISCSI_OP_ASYNC_EVENT: + async_evt = (iscsi_async_evt_hdr_t *)pdu->isp_hdr; + switch (async_evt->opcode) { + case ISCSI_ASYNC_EVENT_REQUEST_LOGOUT: + idm_conn_rx_pdu_event(ic, CE_ASYNC_LOGOUT_RCV, + (uintptr_t)pdu); + break; + case ISCSI_ASYNC_EVENT_DROPPING_CONNECTION: + idm_conn_rx_pdu_event(ic, CE_ASYNC_DROP_CONN_RCV, + (uintptr_t)pdu); + break; + case ISCSI_ASYNC_EVENT_DROPPING_ALL_CONNECTIONS: + idm_conn_rx_pdu_event(ic, CE_ASYNC_DROP_ALL_CONN_RCV, + (uintptr_t)pdu); + break; + case ISCSI_ASYNC_EVENT_SCSI_EVENT: + case ISCSI_ASYNC_EVENT_PARAM_NEGOTIATION: + default: + idm_conn_rx_pdu_event(ic, CE_MISC_RX, + (uintptr_t)pdu); + break; + } + break; + case ISCSI_OP_SCSI_CMD: + /* + * Consider this scenario: We are a target connection + * in "in login" state and a "login success sent" event has + * been generated but not yet handled. Since we've sent + * the login response but we haven't actually transitioned + * to FFP mode we might conceivably receive a SCSI command + * from the initiator before we are ready. We are actually + * in FFP we just don't know it yet -- to address this we + * can generate an event corresponding to the SCSI command. + * At the point when the event is handled by the state + * machine the login request will have been handled and we + * should be in FFP. If we are not in FFP by that time + * we can reject the SCSI command with a protocol error. + * + * This scenario only applies to the target. + */ + case ISCSI_OP_SCSI_DATA: + case ISCSI_OP_SCSI_DATA_RSP: + case ISCSI_OP_RTT_RSP: + case ISCSI_OP_SNACK_CMD: + case ISCSI_OP_NOOP_IN: + case ISCSI_OP_NOOP_OUT: + case ISCSI_OP_TEXT_CMD: + case ISCSI_OP_TEXT_RSP: + case ISCSI_OP_REJECT_MSG: + case ISCSI_OP_SCSI_TASK_MGT_MSG: + case ISCSI_OP_SCSI_TASK_MGT_RSP: + /* Validate received PDU against current state */ + idm_conn_rx_pdu_event(ic, CE_MISC_RX, + (uintptr_t)pdu); + break; + } + mutex_exit(&ic->ic_state_mutex); +} + +void +idm_pdu_tx_forward(idm_conn_t *ic, idm_pdu_t *pdu) +{ + (*ic->ic_transport_ops->it_tx_pdu)(ic, pdu); +} + +boolean_t +idm_pdu_rx_forward_ffp(idm_conn_t *ic, idm_pdu_t *pdu) +{ + /* + * If this is an FFP request, call the appropriate handler + * and return B_TRUE, otherwise return B_FALSE. + */ + switch (IDM_PDU_OPCODE(pdu)) { + case ISCSI_OP_SCSI_CMD: + (*ic->ic_conn_ops.icb_rx_scsi_cmd)(ic, pdu); + return (B_TRUE); + case ISCSI_OP_SCSI_RSP: + (*ic->ic_conn_ops.icb_rx_scsi_rsp)(ic, pdu); + return (B_TRUE); + case ISCSI_OP_SCSI_DATA: + (*ic->ic_transport_ops->it_rx_dataout)(ic, pdu); + return (B_TRUE); + case ISCSI_OP_SCSI_DATA_RSP: + (*ic->ic_transport_ops->it_rx_datain)(ic, pdu); + return (B_TRUE); + case ISCSI_OP_RTT_RSP: + (*ic->ic_transport_ops->it_rx_rtt)(ic, pdu); + return (B_TRUE); + case ISCSI_OP_SCSI_TASK_MGT_MSG: + case ISCSI_OP_SCSI_TASK_MGT_RSP: + case ISCSI_OP_TEXT_CMD: + case ISCSI_OP_TEXT_RSP: + case ISCSI_OP_NOOP_OUT: + case ISCSI_OP_NOOP_IN: + (*ic->ic_conn_ops.icb_rx_misc)(ic, pdu); + return (B_TRUE); + default: + return (B_FALSE); + } + /*NOTREACHED*/ +} + +void +idm_pdu_rx_forward(idm_conn_t *ic, idm_pdu_t *pdu) +{ + /* + * Some PDU's specific to FFP get special handling. This function + * will normally never be called in FFP with an FFP PDU since this + * is a slow path but in can happen on the target side during + * the transition to FFP. We primarily call + * idm_pdu_rx_forward_ffp here to avoid code duplication. + */ + if (idm_pdu_rx_forward_ffp(ic, pdu) == B_FALSE) { + /* + * Non-FFP PDU, use generic RC handler + */ + (*ic->ic_conn_ops.icb_rx_misc)(ic, pdu); + } +} + +void +idm_parse_login_rsp(idm_conn_t *ic, idm_pdu_t *login_rsp_pdu, boolean_t rx) +{ + iscsi_login_rsp_hdr_t *login_rsp = + (iscsi_login_rsp_hdr_t *)login_rsp_pdu->isp_hdr; + idm_conn_event_t new_event; + + if (login_rsp->status_class == ISCSI_STATUS_CLASS_SUCCESS) { + if (!(login_rsp->flags & ISCSI_FLAG_LOGIN_CONTINUE) && + (login_rsp->flags & ISCSI_FLAG_LOGIN_TRANSIT) && + (ISCSI_LOGIN_NEXT_STAGE(login_rsp->flags) == + ISCSI_FULL_FEATURE_PHASE)) { + new_event = (rx ? CE_LOGIN_SUCCESS_RCV : + CE_LOGIN_SUCCESS_SND); + } else { + new_event = (rx ? CE_MISC_RX : CE_MISC_TX); + } + } else { + new_event = (rx ? CE_LOGIN_FAIL_RCV : CE_LOGIN_FAIL_SND); + } + + if (rx) { + idm_conn_rx_pdu_event(ic, new_event, (uintptr_t)login_rsp_pdu); + } else { + idm_conn_tx_pdu_event(ic, new_event, (uintptr_t)login_rsp_pdu); + } +} + + +void +idm_parse_logout_req(idm_conn_t *ic, idm_pdu_t *logout_req_pdu, boolean_t rx) +{ + iscsi_logout_hdr_t *logout_req = + (iscsi_logout_hdr_t *)logout_req_pdu->isp_hdr; + idm_conn_event_t new_event; + uint8_t reason = + (logout_req->flags & ISCSI_FLAG_LOGOUT_REASON_MASK); + + /* + * For a normal logout (close connection or close session) IDM + * will terminate processing of all tasks completing the tasks + * back to the client with a status indicating the connection + * was logged out. These tasks do not get completed. + * + * For a "close connection for recovery logout) IDM suspends + * processing of all tasks and completes them back to the client + * with a status indicating connection was logged out for + * recovery. Both initiator and target hang onto these tasks. + * When we add ERL2 support IDM will need to provide mechanisms + * to change the task and buffer associations to a new connection. + * + * This code doesn't address the possibility of MC/S. We'll + * need to decide how the separate connections get handled + * in that case. One simple option is to make the client + * generate the events for the other connections. + */ + if (reason == ISCSI_LOGOUT_REASON_CLOSE_SESSION) { + new_event = + (rx ? CE_LOGOUT_SESSION_RCV : CE_LOGOUT_SESSION_SND); + } else if ((reason == ISCSI_LOGOUT_REASON_CLOSE_CONNECTION) || + (reason == ISCSI_LOGOUT_REASON_RECOVERY)) { + /* Check logout CID against this connection's CID */ + if (ntohs(logout_req->cid) == ic->ic_login_cid) { + /* Logout is for this connection */ + new_event = (rx ? CE_LOGOUT_THIS_CONN_RCV : + CE_LOGOUT_THIS_CONN_SND); + } else { + /* + * Logout affects another connection. This is not + * a relevant event for this connection so we'll + * just treat it as a normal PDU event. Client + * will need to lookup the other connection and + * generate the event. + */ + new_event = (rx ? CE_MISC_RX : CE_MISC_TX); + } + } else { + /* Invalid reason code */ + new_event = (rx ? CE_RX_PROTOCOL_ERROR : CE_TX_PROTOCOL_ERROR); + } + + if (rx) { + idm_conn_rx_pdu_event(ic, new_event, (uintptr_t)logout_req_pdu); + } else { + idm_conn_tx_pdu_event(ic, new_event, (uintptr_t)logout_req_pdu); + } +} + + + +void +idm_parse_logout_rsp(idm_conn_t *ic, idm_pdu_t *logout_rsp_pdu, boolean_t rx) +{ + idm_conn_event_t new_event; + iscsi_logout_rsp_hdr_t *logout_rsp = + (iscsi_logout_rsp_hdr_t *)logout_rsp_pdu->isp_hdr; + + if (logout_rsp->response == ISCSI_STATUS_CLASS_SUCCESS) { + new_event = rx ? CE_LOGOUT_SUCCESS_RCV : CE_LOGOUT_SUCCESS_SND; + } else { + new_event = rx ? CE_LOGOUT_FAIL_RCV : CE_LOGOUT_FAIL_SND; + } + + if (rx) { + idm_conn_rx_pdu_event(ic, new_event, (uintptr_t)logout_rsp_pdu); + } else { + idm_conn_tx_pdu_event(ic, new_event, (uintptr_t)logout_rsp_pdu); + } +} + +/* + * idm_svc_conn_create() + * Transport-agnostic service connection creation, invoked from the transport + * layer. + */ +idm_status_t +idm_svc_conn_create(idm_svc_t *is, idm_transport_type_t tt, + idm_conn_t **ic_result) +{ + idm_conn_t *ic; + idm_status_t rc; + + ic = idm_conn_create_common(CONN_TYPE_TGT, tt, + &is->is_svc_req.sr_conn_ops); + ic->ic_svc_binding = is; + + /* + * Prepare connection state machine + */ + if ((rc = idm_conn_sm_init(ic)) != 0) { + idm_conn_destroy_common(ic); + return (rc); + } + + + *ic_result = ic; + + mutex_enter(&idm.idm_global_mutex); + list_insert_tail(&idm.idm_tgt_conn_list, ic); + idm.idm_tgt_conn_count++; + mutex_exit(&idm.idm_global_mutex); + + return (0); +} + +void +idm_svc_conn_destroy(idm_conn_t *ic) +{ + mutex_enter(&idm.idm_global_mutex); + list_remove(&idm.idm_tgt_conn_list, ic); + idm.idm_tgt_conn_count--; + mutex_exit(&idm.idm_global_mutex); + + idm_conn_sm_fini(ic); + + if (ic->ic_transport_private != NULL) { + ic->ic_transport_ops->it_tgt_conn_destroy(ic); + } + idm_conn_destroy_common(ic); +} + +/* + * idm_conn_create_common() + * + * Allocate and initialize IDM connection context + */ +idm_conn_t * +idm_conn_create_common(idm_conn_type_t conn_type, idm_transport_type_t tt, + idm_conn_ops_t *conn_ops) +{ + idm_conn_t *ic; + idm_transport_t *it; + idm_transport_type_t type; + + for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) { + it = &idm_transport_list[type]; + + if ((it->it_ops != NULL) && (it->it_type == tt)) + break; + } + ASSERT(it->it_type == tt); + if (it->it_type != tt) + return (NULL); + + ic = kmem_zalloc(sizeof (idm_conn_t), KM_SLEEP); + + /* Initialize data */ + ic->ic_conn_type = conn_type; + ic->ic_conn_ops = *conn_ops; + ic->ic_transport_ops = it->it_ops; + ic->ic_transport_type = tt; + ic->ic_transport_private = NULL; /* Set by transport service */ + ic->ic_internal_cid = idm_cid_alloc(); + if (ic->ic_internal_cid == 0) { + kmem_free(ic, sizeof (idm_conn_t)); + return (NULL); + } + mutex_init(&ic->ic_mutex, NULL, MUTEX_DEFAULT, NULL); + cv_init(&ic->ic_cv, NULL, CV_DEFAULT, NULL); + idm_refcnt_init(&ic->ic_refcnt, ic); + + return (ic); +} + +void +idm_conn_destroy_common(idm_conn_t *ic) +{ + idm_refcnt_destroy(&ic->ic_refcnt); + cv_destroy(&ic->ic_cv); + mutex_destroy(&ic->ic_mutex); + idm_cid_free(ic->ic_internal_cid); + + kmem_free(ic, sizeof (idm_conn_t)); +} + +/* + * Invoked from the SM as a result of client's invocation of + * idm_ini_conn_connect() + */ +idm_status_t +idm_ini_conn_finish(idm_conn_t *ic) +{ + /* invoke transport-specific connection */ + return (ic->ic_transport_ops->it_ini_conn_connect(ic)); +} + +idm_status_t +idm_tgt_conn_finish(idm_conn_t *ic) +{ + idm_status_t rc; + + rc = idm_notify_client(ic, CN_CONNECT_ACCEPT, NULL); + if (rc != IDM_STATUS_SUCCESS) { + return (IDM_STATUS_REJECT); + } + + /* Target client is ready to receive a login, start connection */ + return (ic->ic_transport_ops->it_tgt_conn_connect(ic)); +} + +idm_transport_t * +idm_transport_lookup(idm_conn_req_t *cr) +{ + idm_transport_type_t type; + idm_transport_t *it; + idm_transport_caps_t caps; + + /* + * Make sure all available transports are setup. We call this now + * instead of at initialization time in case IB has become available + * since we started (hotplug, etc). + */ + idm_transport_setup(cr->cr_li); + + /* Determine the transport for this connection */ + for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) { + it = &idm_transport_list[type]; + + if (it->it_ops == NULL) { + /* transport is not registered */ + continue; + } + + if (it->it_ops->it_conn_is_capable(cr, &caps)) { + return (it); + } + } + + ASSERT(0); + return (NULL); /* Make gcc happy */ +} + +void +idm_transport_setup(ldi_ident_t li) +{ + idm_transport_type_t type; + idm_transport_t *it; + int rc; + + for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) { + it = &idm_transport_list[type]; + /* + * We may want to store the LDI handle in the idm_svc_t + * and then allow multiple calls to ldi_open_by_name. This + * would enable the LDI code to track who has the device open + * which could be useful in the case where we have multiple + * services and perhaps also have initiator and target opening + * the transport simultaneously. For now we stick with the + * plan. + */ + if (it->it_ops == NULL) { + /* transport is not ready, try to initialize it */ + if (it->it_type == IDM_TRANSPORT_TYPE_SOCKETS) { + idm_so_init(it); + } else { + rc = ldi_open_by_name(it->it_device_path, + FREAD | FWRITE, kcred, &it->it_ldi_hdl, li); + /* + * If the open is successful we will have + * filled in the LDI handle in the transport + * table and we expect that the transport + * registered itself. + */ + if (rc != 0) { + it->it_ldi_hdl = NULL; + } + } + } + } +} + +/* + * ID pool code. We use this to generate unique structure identifiers without + * searching the existing structures. This avoids the need to lock entire + * sets of structures at inopportune times. Adapted from the CIFS server code. + * + * A pool of IDs is a pool of 16 bit numbers. It is implemented as a bitmap. + * A bit set to '1' indicates that that particular value has been allocated. + * The allocation process is done shifting a bit through the whole bitmap. + * The current position of that index bit is kept in the idm_idpool_t + * structure and represented by a byte index (0 to buffer size minus 1) and + * a bit index (0 to 7). + * + * The pools start with a size of 8 bytes or 64 IDs. Each time the pool runs + * out of IDs its current size is doubled until it reaches its maximum size + * (8192 bytes or 65536 IDs). The IDs 0 and 65535 are never given out which + * means that a pool can have a maximum number of 65534 IDs available. + */ + +static int +idm_idpool_increment( + idm_idpool_t *pool) +{ + uint8_t *new_pool; + uint32_t new_size; + + ASSERT(pool->id_magic == IDM_IDPOOL_MAGIC); + + new_size = pool->id_size * 2; + if (new_size <= IDM_IDPOOL_MAX_SIZE) { + new_pool = kmem_alloc(new_size / 8, KM_NOSLEEP); + if (new_pool) { + bzero(new_pool, new_size / 8); + bcopy(pool->id_pool, new_pool, pool->id_size / 8); + kmem_free(pool->id_pool, pool->id_size / 8); + pool->id_pool = new_pool; + pool->id_free_counter += new_size - pool->id_size; + pool->id_max_free_counter += new_size - pool->id_size; + pool->id_size = new_size; + pool->id_idx_msk = (new_size / 8) - 1; + if (new_size >= IDM_IDPOOL_MAX_SIZE) { + /* id -1 made unavailable */ + pool->id_pool[pool->id_idx_msk] = 0x80; + pool->id_free_counter--; + pool->id_max_free_counter--; + } + return (0); + } + } + return (-1); +} + +/* + * idm_idpool_constructor + * + * This function initializes the pool structure provided. + */ + +int +idm_idpool_create(idm_idpool_t *pool) +{ + + ASSERT(pool->id_magic != IDM_IDPOOL_MAGIC); + + pool->id_size = IDM_IDPOOL_MIN_SIZE; + pool->id_idx_msk = (IDM_IDPOOL_MIN_SIZE / 8) - 1; + pool->id_free_counter = IDM_IDPOOL_MIN_SIZE - 1; + pool->id_max_free_counter = IDM_IDPOOL_MIN_SIZE - 1; + pool->id_bit = 0x02; + pool->id_bit_idx = 1; + pool->id_idx = 0; + pool->id_pool = (uint8_t *)kmem_alloc((IDM_IDPOOL_MIN_SIZE / 8), + KM_SLEEP); + bzero(pool->id_pool, (IDM_IDPOOL_MIN_SIZE / 8)); + /* -1 id made unavailable */ + pool->id_pool[0] = 0x01; /* id 0 made unavailable */ + mutex_init(&pool->id_mutex, NULL, MUTEX_DEFAULT, NULL); + pool->id_magic = IDM_IDPOOL_MAGIC; + return (0); +} + +/* + * idm_idpool_destructor + * + * This function tears down and frees the resources associated with the + * pool provided. + */ + +void +idm_idpool_destroy(idm_idpool_t *pool) +{ + ASSERT(pool->id_magic == IDM_IDPOOL_MAGIC); + ASSERT(pool->id_free_counter == pool->id_max_free_counter); + pool->id_magic = (uint32_t)~IDM_IDPOOL_MAGIC; + mutex_destroy(&pool->id_mutex); + kmem_free(pool->id_pool, (size_t)(pool->id_size / 8)); +} + +/* + * idm_idpool_alloc + * + * This function allocates an ID from the pool provided. + */ +int +idm_idpool_alloc(idm_idpool_t *pool, uint16_t *id) +{ + uint32_t i; + uint8_t bit; + uint8_t bit_idx; + uint8_t byte; + + ASSERT(pool->id_magic == IDM_IDPOOL_MAGIC); + + mutex_enter(&pool->id_mutex); + if ((pool->id_free_counter == 0) && idm_idpool_increment(pool)) { + mutex_exit(&pool->id_mutex); + return (-1); + } + + i = pool->id_size; + while (i) { + bit = pool->id_bit; + bit_idx = pool->id_bit_idx; + byte = pool->id_pool[pool->id_idx]; + while (bit) { + if (byte & bit) { + bit = bit << 1; + bit_idx++; + continue; + } + pool->id_pool[pool->id_idx] |= bit; + *id = (uint16_t)(pool->id_idx * 8 + (uint32_t)bit_idx); + pool->id_free_counter--; + pool->id_bit = bit; + pool->id_bit_idx = bit_idx; + mutex_exit(&pool->id_mutex); + return (0); + } + pool->id_bit = 1; + pool->id_bit_idx = 0; + pool->id_idx++; + pool->id_idx &= pool->id_idx_msk; + --i; + } + /* + * This section of code shouldn't be reached. If there are IDs + * available and none could be found there's a problem. + */ + ASSERT(0); + mutex_exit(&pool->id_mutex); + return (-1); +} + +/* + * idm_idpool_free + * + * This function frees the ID provided. + */ +void +idm_idpool_free(idm_idpool_t *pool, uint16_t id) +{ + ASSERT(pool->id_magic == IDM_IDPOOL_MAGIC); + ASSERT(id != 0); + ASSERT(id != 0xFFFF); + + mutex_enter(&pool->id_mutex); + if (pool->id_pool[id >> 3] & (1 << (id & 7))) { + pool->id_pool[id >> 3] &= ~(1 << (id & 7)); + pool->id_free_counter++; + ASSERT(pool->id_free_counter <= pool->id_max_free_counter); + mutex_exit(&pool->id_mutex); + return; + } + /* Freeing a free ID. */ + ASSERT(0); + mutex_exit(&pool->id_mutex); +} + +uint32_t +idm_cid_alloc(void) +{ + /* + * ID pool works with 16-bit identifiers right now. That should + * be plenty since we will probably never have more than 2^16 + * connections simultaneously. + */ + uint16_t cid16; + + if (idm_idpool_alloc(&idm.idm_conn_id_pool, &cid16) == -1) { + return (0); /* Fail */ + } + + return ((uint32_t)cid16); +} + +void +idm_cid_free(uint32_t cid) +{ + idm_idpool_free(&idm.idm_conn_id_pool, (uint16_t)cid); +} + + +/* + * Code for generating the header and data digests + * + * This is the CRC-32C table + * Generated with: + * width = 32 bits + * poly = 0x1EDC6F41 + * reflect input bytes = true + * reflect output bytes = true + */ + +uint32_t idm_crc32c_table[256] = +{ + 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, + 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, + 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, + 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, + 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, + 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, + 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, + 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, + 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, + 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, + 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, + 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, + 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, + 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, + 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, + 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, + 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, + 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, + 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, + 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, + 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, + 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, + 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, + 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, + 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, + 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, + 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, + 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, + 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, + 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, + 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, + 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, + 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, + 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, + 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, + 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, + 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, + 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, + 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, + 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, + 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, + 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, + 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, + 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, + 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, + 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, + 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, + 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, + 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, + 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, + 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, + 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, + 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, + 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, + 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, + 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, + 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, + 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, + 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, + 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, + 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351 +}; + +/* + * iscsi_crc32c - Steps through buffer one byte at at time, calculates + * reflected crc using table. + */ +uint32_t +idm_crc32c(void *address, unsigned long length) +{ + uint8_t *buffer = address; + uint32_t crc = 0xffffffff, result; +#ifdef _BIG_ENDIAN + uint8_t byte0, byte1, byte2, byte3; +#endif + + ASSERT(address != NULL); + + while (length--) { + crc = idm_crc32c_table[(crc ^ *buffer++) & 0xFFL] ^ + (crc >> 8); + } + result = crc ^ 0xffffffff; + +#ifdef _BIG_ENDIAN + byte0 = (uint8_t)(result & 0xFF); + byte1 = (uint8_t)((result >> 8) & 0xFF); + byte2 = (uint8_t)((result >> 16) & 0xFF); + byte3 = (uint8_t)((result >> 24) & 0xFF); + result = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3); +#endif /* _BIG_ENDIAN */ + + return (result); +} + + +/* + * idm_crc32c_continued - Continues stepping through buffer one + * byte at at time, calculates reflected crc using table. + */ +uint32_t +idm_crc32c_continued(void *address, unsigned long length, uint32_t crc) +{ + uint8_t *buffer = address; + uint32_t result; +#ifdef _BIG_ENDIAN + uint8_t byte0, byte1, byte2, byte3; +#endif + + ASSERT(address != NULL); + +#ifdef _BIG_ENDIAN + byte0 = (uint8_t)((crc >> 24) & 0xFF); + byte1 = (uint8_t)((crc >> 16) & 0xFF); + byte2 = (uint8_t)((crc >> 8) & 0xFF); + byte3 = (uint8_t)(crc & 0xFF); + crc = ((byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0); +#endif + + crc = crc ^ 0xffffffff; + while (length--) { + crc = idm_crc32c_table[(crc ^ *buffer++) & 0xFFL] ^ + (crc >> 8); + } + result = crc ^ 0xffffffff; + +#ifdef _BIG_ENDIAN + byte0 = (uint8_t)(result & 0xFF); + byte1 = (uint8_t)((result >> 8) & 0xFF); + byte2 = (uint8_t)((result >> 16) & 0xFF); + byte3 = (uint8_t)((result >> 24) & 0xFF); + result = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3); +#endif + return (result); +} + +/* ARGSUSED */ +int +idm_task_constructor(void *hdl, void *arg, int flags) +{ + idm_task_t *idt = (idm_task_t *)hdl; + uint32_t next_task; + + mutex_init(&idt->idt_mutex, NULL, MUTEX_DEFAULT, NULL); + + /* Find the next free task ID */ + rw_enter(&idm.idm_taskid_table_lock, RW_WRITER); + next_task = idm.idm_taskid_next; + while (idm.idm_taskid_table[next_task]) { + next_task++; + if (next_task == idm.idm_taskid_max) + next_task = 0; + if (next_task == idm.idm_taskid_next) { + rw_exit(&idm.idm_taskid_table_lock); + return (-1); + } + } + + idm.idm_taskid_table[next_task] = idt; + idm.idm_taskid_next = (next_task + 1) % idm.idm_taskid_max; + rw_exit(&idm.idm_taskid_table_lock); + + idt->idt_tt = next_task; + + list_create(&idt->idt_inbufv, sizeof (idm_buf_t), + offsetof(idm_buf_t, idb_buflink)); + list_create(&idt->idt_outbufv, sizeof (idm_buf_t), + offsetof(idm_buf_t, idb_buflink)); + idm_refcnt_init(&idt->idt_refcnt, idt); + + /* + * Set the transport header pointer explicitly. This removes the + * need for per-transport header allocation, which simplifies cache + * init considerably. If at a later date we have an additional IDM + * transport that requires a different size, we'll revisit this. + */ + idt->idt_transport_hdr = (void *)(idt + 1); /* pointer arithmetic */ + + return (0); +} + +/* ARGSUSED */ +void +idm_task_destructor(void *hdl, void *arg) +{ + idm_task_t *idt = (idm_task_t *)hdl; + + /* Remove the task from the ID table */ + rw_enter(&idm.idm_taskid_table_lock, RW_WRITER); + idm.idm_taskid_table[idt->idt_tt] = NULL; + rw_exit(&idm.idm_taskid_table_lock); + + /* free the inbuf and outbuf */ + idm_refcnt_destroy(&idt->idt_refcnt); + list_destroy(&idt->idt_inbufv); + list_destroy(&idt->idt_outbufv); + + mutex_destroy(&idt->idt_mutex); +} + +/* + * idm_listbuf_insert searches from the back of the list looking for the + * insertion point. + */ +void +idm_listbuf_insert(list_t *lst, idm_buf_t *buf) +{ + idm_buf_t *idb; + + /* iterate through the list to find the insertion point */ + for (idb = list_tail(lst); idb != NULL; idb = list_prev(lst, idb)) { + + if (idb->idb_bufoffset < buf->idb_bufoffset) { + + list_insert_after(lst, idb, buf); + return; + } + } + + /* add the buf to the head of the list */ + list_insert_head(lst, buf); + +} + +/*ARGSUSED*/ +void +idm_wd_thread(void *arg) +{ + idm_conn_t *ic; + clock_t wake_time; + clock_t idle_time; + + mutex_enter(&idm.idm_global_mutex); + idm.idm_wd_thread_running = B_TRUE; + idm.idm_wd_thread_did = idm.idm_wd_thread->t_did; + + cv_signal(&idm.idm_wd_cv); + + while (idm.idm_wd_thread_running) { + for (ic = list_head(&idm.idm_tgt_conn_list); + ic != NULL; + ic = list_next(&idm.idm_tgt_conn_list, ic)) { + idle_time = ddi_get_lbolt() - ic->ic_timestamp; + + /* + * If there hasn't been any activity on this + * connection for the specified period then + * drop the connection. We expect the initiator + * to keep the connection alive if it wants the + * connection to stay open. + * + * If it turns out to be desireable to take a + * more active role in maintaining the connect + * we could add a client callback to send + * a "keepalive" kind of message (no doubt a nop) + * and fire that on a shorter timer. + */ + if (TICK_TO_SEC(idle_time) > + IDM_TRANSPORT_FAIL_IDLE_TIMEOUT) { + /* + * Only send the transport fail if we're in + * FFP. State machine timers should handle + * problems in non-ffp states. + */ + if (ic->ic_ffp) { + mutex_exit(&idm.idm_global_mutex); + IDM_SM_LOG(CE_WARN, "idm_wd_thread: " + "conn %p idle for %d seconds, " + "sending CE_TRANSPORT_FAIL", + (void *)ic, (int)idle_time); + idm_conn_event(ic, CE_TRANSPORT_FAIL, + NULL); + mutex_enter(&idm.idm_global_mutex); + } + } + } + + wake_time = lbolt + SEC_TO_TICK(IDM_WD_INTERVAL); + (void) cv_timedwait(&idm.idm_wd_cv, &idm.idm_global_mutex, + wake_time); + } + mutex_exit(&idm.idm_global_mutex); + + thread_exit(); +} diff --git a/usr/src/uts/common/io/idm/idm_so.c b/usr/src/uts/common/io/idm/idm_so.c new file mode 100644 index 000000000000..b8c236d749c2 --- /dev/null +++ b/usr/src/uts/common/io/idm/idm_so.c @@ -0,0 +1,2525 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* + * in6addr_any is currently all zeroes, but use the macro in case this + * ever changes. + */ +const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; + +static void idm_sorx_cache_pdu_cb(idm_pdu_t *pdu, idm_status_t status); +static void idm_sorx_addl_pdu_cb(idm_pdu_t *pdu, idm_status_t status); +static void idm_sotx_cache_pdu_cb(idm_pdu_t *pdu, idm_status_t status); + +static idm_status_t idm_so_conn_create_common(idm_conn_t *ic, + struct sonode *new_so); +static void idm_so_conn_destroy_common(idm_conn_t *ic); +static void idm_so_conn_connect_common(idm_conn_t *ic); + +static void idm_set_ini_preconnect_options(idm_so_conn_t *sc); +static void idm_set_ini_postconnect_options(idm_so_conn_t *sc); +static void idm_set_tgt_connect_options(struct sonode *sonode); +static idm_status_t idm_i_so_tx(idm_pdu_t *pdu); + +static idm_status_t idm_sorecvdata(idm_conn_t *ic, idm_pdu_t *pdu); +static idm_status_t idm_so_send_buf_region(idm_task_t *idt, uint8_t opcode, + idm_buf_t *idb, uint32_t buf_region_offset, uint32_t buf_region_length); + +static uint32_t idm_fill_iov(idm_pdu_t *pdu, idm_buf_t *idb, + uint32_t ro, uint32_t dlength); + +static idm_status_t idm_so_handle_digest(idm_conn_t *it, + nvpair_t *digest_choice, const idm_kv_xlate_t *ikvx); + +/* + * Transport ops prototypes + */ +static void idm_so_tx(idm_conn_t *ic, idm_pdu_t *pdu); +static idm_status_t idm_so_buf_tx_to_ini(idm_task_t *idt, idm_buf_t *idb); +static idm_status_t idm_so_buf_rx_from_ini(idm_task_t *idt, idm_buf_t *idb); +static void idm_so_rx_datain(idm_conn_t *ic, idm_pdu_t *pdu); +static void idm_so_rx_rtt(idm_conn_t *ic, idm_pdu_t *pdu); +static void idm_so_rx_dataout(idm_conn_t *ic, idm_pdu_t *pdu); +static idm_status_t idm_so_free_task_rsrc(idm_task_t *idt); +static kv_status_t idm_so_negotiate_key_values(idm_conn_t *it, + nvlist_t *request_nvl, nvlist_t *response_nvl, nvlist_t *negotiated_nvl); +static idm_status_t idm_so_notice_key_values(idm_conn_t *it, + nvlist_t *negotiated_nvl); +static boolean_t idm_so_conn_is_capable(idm_conn_req_t *ic, + idm_transport_caps_t *caps); +static idm_status_t idm_so_buf_alloc(idm_buf_t *idb, uint64_t buflen); +static void idm_so_buf_free(idm_buf_t *idb); +static idm_status_t idm_so_buf_setup(idm_buf_t *idb); +static void idm_so_buf_teardown(idm_buf_t *idb); +static idm_status_t idm_so_tgt_svc_create(idm_svc_req_t *sr, idm_svc_t *is); +static void idm_so_tgt_svc_destroy(idm_svc_t *is); +static idm_status_t idm_so_tgt_svc_online(idm_svc_t *is); +static void idm_so_tgt_svc_offline(idm_svc_t *is); +static void idm_so_tgt_conn_destroy(idm_conn_t *ic); +static idm_status_t idm_so_tgt_conn_connect(idm_conn_t *ic); +static void idm_so_conn_disconnect(idm_conn_t *ic); +static idm_status_t idm_so_ini_conn_create(idm_conn_req_t *cr, idm_conn_t *ic); +static void idm_so_ini_conn_destroy(idm_conn_t *ic); +static idm_status_t idm_so_ini_conn_connect(idm_conn_t *ic); + +/* + * IDM Native Sockets transport operations + */ +static +idm_transport_ops_t idm_so_transport_ops = { + idm_so_tx, /* it_tx_pdu */ + idm_so_buf_tx_to_ini, /* it_buf_tx_to_ini */ + idm_so_buf_rx_from_ini, /* it_buf_rx_from_ini */ + idm_so_rx_datain, /* it_rx_datain */ + idm_so_rx_rtt, /* it_rx_rtt */ + idm_so_rx_dataout, /* it_rx_dataout */ + NULL, /* it_alloc_conn_rsrc */ + NULL, /* it_free_conn_rsrc */ + NULL, /* it_tgt_enable_datamover */ + NULL, /* it_ini_enable_datamover */ + NULL, /* it_conn_terminate */ + idm_so_free_task_rsrc, /* it_free_task_rsrc */ + idm_so_negotiate_key_values, /* it_negotiate_key_values */ + idm_so_notice_key_values, /* it_notice_key_values */ + idm_so_conn_is_capable, /* it_conn_is_capable */ + idm_so_buf_alloc, /* it_buf_alloc */ + idm_so_buf_free, /* it_buf_free */ + idm_so_buf_setup, /* it_buf_setup */ + idm_so_buf_teardown, /* it_buf_teardown */ + idm_so_tgt_svc_create, /* it_tgt_svc_create */ + idm_so_tgt_svc_destroy, /* it_tgt_svc_destroy */ + idm_so_tgt_svc_online, /* it_tgt_svc_online */ + idm_so_tgt_svc_offline, /* it_tgt_svc_offline */ + idm_so_tgt_conn_destroy, /* it_tgt_conn_destroy */ + idm_so_tgt_conn_connect, /* it_tgt_conn_connect */ + idm_so_conn_disconnect, /* it_tgt_conn_disconnect */ + idm_so_ini_conn_create, /* it_ini_conn_create */ + idm_so_ini_conn_destroy, /* it_ini_conn_destroy */ + idm_so_ini_conn_connect, /* it_ini_conn_connect */ + idm_so_conn_disconnect /* it_ini_conn_disconnect */ +}; + +/* + * idm_so_init() + * Sockets transport initialization + */ +void +idm_so_init(idm_transport_t *it) +{ + /* Cache for IDM Data and R2T Transmit PDU's */ + idm.idm_sotx_pdu_cache = kmem_cache_create("idm_tx_pdu_cache", + sizeof (idm_pdu_t) + sizeof (iscsi_hdr_t), 8, + &idm_sotx_pdu_constructor, NULL, NULL, NULL, NULL, KM_SLEEP); + + /* Cache for IDM Receive PDU's */ + idm.idm_sorx_pdu_cache = kmem_cache_create("idm_rx_pdu_cache", + sizeof (idm_pdu_t) + IDM_SORX_CACHE_HDRLEN, 8, + &idm_sorx_pdu_constructor, NULL, NULL, NULL, NULL, KM_SLEEP); + + /* Set the sockets transport ops */ + it->it_ops = &idm_so_transport_ops; +} + +/* + * idm_so_fini() + * Sockets transport teardown + */ +void +idm_so_fini(void) +{ + kmem_cache_destroy(idm.idm_sotx_pdu_cache); + kmem_cache_destroy(idm.idm_sorx_pdu_cache); +} + +struct sonode * +idm_socreate(int domain, int type, int protocol) +{ + vnode_t *dvp; + vnode_t *vp; + struct snode *csp; + int err; + major_t maj; + + if ((vp = solookup(domain, type, protocol, NULL, &err)) == NULL) { + + /* + * solookup calls sogetvp if the vp is not found in the cache. + * Since the call to sogetvp is hardwired to use USERSPACE + * and declared static we'll do the work here instead. + */ + err = lookupname(type == SOCK_STREAM ? "/dev/tcp" : "/dev/udp", + UIO_SYSSPACE, FOLLOW, NULLVPP, &vp); + if (err != 0) + return (NULL); + + /* Check that it is the correct vnode */ + if (vp->v_type != VCHR) { + VN_RELE(vp); + return (NULL); + } + + csp = VTOS(VTOS(vp)->s_commonvp); + if (!(csp->s_flag & SDIPSET)) { + char *pathname = kmem_alloc(MAXPATHLEN, KM_SLEEP); + + err = ddi_dev_pathname(vp->v_rdev, S_IFCHR, + pathname); + if (err == 0) { + err = devfs_lookupname(pathname, NULLVPP, + &dvp); + } + VN_RELE(vp); + kmem_free(pathname, MAXPATHLEN); + if (err != 0) { + return (NULL); + } + vp = dvp; + } + + maj = getmajor(vp->v_rdev); + if (!STREAMSTAB(maj)) { + VN_RELE(vp); + return (NULL); + } + } + return (socreate(vp, domain, type, protocol, SOV_DEFAULT, NULL, &err)); +} + +/* + * idm_soshutdown will disconnect the socket and prevent subsequent PDU + * reception and transmission. The sonode still exists but its state + * gets modified to indicate it is no longer connected. Calls to + * idm_sorecv/idm_iov_sorecv will return so idm_soshutdown can be used + * regain control of a thread stuck in idm_sorecv. + */ +void +idm_soshutdown(struct sonode *so) +{ + (void) soshutdown(so, SHUT_RDWR); +} + +/* + * idm_sodestroy releases all resources associated with a socket previously + * created with idm_socreate. The socket must be shutdown using + * idm_soshutdown before the socket is destroyed with idm_sodestroy, + * otherwise undefined behavior will result. + */ +void +idm_sodestroy(struct sonode *so) +{ + vnode_t *vp = SOTOV(so); + + (void) VOP_CLOSE(vp, 0, 1, 0, kcred, NULL); + + VN_RELE(vp); +} + +/* + * IP address filter functions to flag addresses that should not + * go out to initiators through discovery. + */ +static boolean_t +idm_v4_addr_okay(struct in_addr *in_addr) +{ + in_addr_t addr = ntohl(in_addr->s_addr); + + if ((INADDR_NONE == addr) || + (IN_MULTICAST(addr)) || + ((addr >> IN_CLASSA_NSHIFT) == 0) || + ((addr >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)) { + return (B_FALSE); + } + return (B_TRUE); +} + +static boolean_t +idm_v6_addr_okay(struct in6_addr *addr6) +{ + + if ((IN6_IS_ADDR_UNSPECIFIED(addr6)) || + (IN6_IS_ADDR_LOOPBACK(addr6)) || + (IN6_IS_ADDR_MULTICAST(addr6)) || + (IN6_IS_ADDR_V4MAPPED(addr6)) || + (IN6_IS_ADDR_V4COMPAT(addr6)) || + (IN6_IS_ADDR_LINKLOCAL(addr6))) { + return (B_FALSE); + } + return (B_TRUE); +} + +/* + * idm_get_ipaddr will retrieve a list of IP Addresses which the host is + * configured with by sending down a sequence of kernel ioctl to IP STREAMS. + */ +int +idm_get_ipaddr(idm_addr_list_t **ipaddr_p) +{ + struct sonode *so4, *so6; + vnode_t *vp, *vp4, *vp6; + struct lifnum lifn; + struct lifconf lifc; + struct lifreq *lp; + int rval; + int numifs; + int bufsize; + void *buf; + int i, j, n, rc; + struct sockaddr_storage ss; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + idm_addr_t *ip; + idm_addr_list_t *ipaddr; + int size_ipaddr; + + *ipaddr_p = NULL; + size_ipaddr = 0; + buf = NULL; + + /* create an ipv4 and ipv6 UDP socket */ + if ((so6 = idm_socreate(PF_INET6, SOCK_DGRAM, 0)) == NULL) + return (0); + if ((so4 = idm_socreate(PF_INET, SOCK_DGRAM, 0)) == NULL) { + idm_sodestroy(so6); + return (0); + } + + /* setup the vp's for each socket type */ + vp6 = SOTOV(so6); + vp4 = SOTOV(so4); + /* use vp6 for ioctls with unspecified families by default */ + vp = vp6; + +retry_count: + /* snapshot the current number of interfaces */ + lifn.lifn_family = PF_UNSPEC; + lifn.lifn_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES; + lifn.lifn_count = 0; + if (VOP_IOCTL(vp, SIOCGLIFNUM, (intptr_t)&lifn, FKIOCTL, kcred, + &rval, NULL) != 0) { + goto cleanup; + } + + numifs = lifn.lifn_count; + if (numifs <= 0) { + goto cleanup; + } + + /* allocate extra room in case more interfaces appear */ + numifs += 10; + + /* get the interface names and ip addresses */ + bufsize = numifs * sizeof (struct lifreq); + buf = kmem_alloc(bufsize, KM_SLEEP); + + lifc.lifc_family = AF_UNSPEC; + lifc.lifc_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES; + lifc.lifc_len = bufsize; + lifc.lifc_buf = buf; + rc = VOP_IOCTL(vp, SIOCGLIFCONF, (intptr_t)&lifc, FKIOCTL, kcred, + &rval, NULL); + if (rc != 0) { + goto cleanup; + } + /* if our extra room is used up, try again */ + if (bufsize <= lifc.lifc_len) { + kmem_free(buf, bufsize); + buf = NULL; + goto retry_count; + } + /* calc actual number of ifconfs */ + n = lifc.lifc_len / sizeof (struct lifreq); + + /* get ip address */ + if (n > 0) { + size_ipaddr = sizeof (idm_addr_list_t) + + (n - 1) * sizeof (idm_addr_t); + ipaddr = kmem_zalloc(size_ipaddr, KM_SLEEP); + } else { + goto cleanup; + } + + /* + * Examine the array of interfaces and filter uninteresting ones + */ + for (i = 0, j = 0, lp = lifc.lifc_req; i < n; i++, lp++) { + + /* + * Copy the address as the SIOCGLIFFLAGS ioctl is destructive + */ + ss = lp->lifr_addr; + /* + * fetch the flags using the socket of the correct family + */ + switch (ss.ss_family) { + case AF_INET: + vp = vp4; + break; + case AF_INET6: + vp = vp6; + break; + default: + continue; + } + rc = VOP_IOCTL(vp, SIOCGLIFFLAGS, (intptr_t)lp, FKIOCTL, kcred, + &rval, NULL); + if (rc == 0) { + /* + * If we got the flags, skip uninteresting + * interfaces based on flags + */ + if ((lp->lifr_flags & IFF_UP) != IFF_UP) + continue; + if (lp->lifr_flags & + (IFF_ANYCAST|IFF_NOLOCAL|IFF_DEPRECATED)) + continue; + } + + /* save ip address */ + ip = &ipaddr->al_addrs[j]; + switch (ss.ss_family) { + case AF_INET: + sin = (struct sockaddr_in *)&ss; + if (!idm_v4_addr_okay(&sin->sin_addr)) + continue; + ip->a_addr.i_addr.in4 = sin->sin_addr; + ip->a_addr.i_insize = sizeof (struct in_addr); + break; + case AF_INET6: + sin6 = (struct sockaddr_in6 *)&ss; + if (!idm_v6_addr_okay(&sin6->sin6_addr)) + continue; + ip->a_addr.i_addr.in6 = sin6->sin6_addr; + ip->a_addr.i_insize = sizeof (struct in6_addr); + break; + default: + continue; + } + j++; + } + + if (j == 0) { + /* no valid ifaddr */ + kmem_free(ipaddr, size_ipaddr); + size_ipaddr = 0; + ipaddr = NULL; + } else { + ipaddr->al_out_cnt = j; + } + + +cleanup: + idm_sodestroy(so6); + idm_sodestroy(so4); + + if (buf != NULL) + kmem_free(buf, bufsize); + + *ipaddr_p = ipaddr; + return (size_ipaddr); +} + +int +idm_sorecv(struct sonode *so, void *msg, size_t len) +{ + iovec_t iov; + + ASSERT(so != NULL); + ASSERT(len != 0); + + /* + * Fill in iovec and receive data + */ + iov.iov_base = msg; + iov.iov_len = len; + + return (idm_iov_sorecv(so, &iov, 1, len)); +} + +/* + * idm_sosendto - Sends a buffered data on a non-connected socket. + * + * This function puts the data provided on the wire by calling sosendmsg. + * It will return only when all the data has been sent or if an error + * occurs. + * + * Returns 0 for success, the socket errno value if sosendmsg fails, and + * -1 if sosendmsg returns success but uio_resid != 0 + */ +int +idm_sosendto(struct sonode *so, void *buff, size_t len, + struct sockaddr *name, socklen_t namelen) +{ + struct msghdr msg; + struct uio uio; + struct iovec iov[1]; + int error; + + iov[0].iov_base = buff; + iov[0].iov_len = len; + + /* Initialization of the message header. */ + bzero(&msg, sizeof (msg)); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + /* Initialization of the uio structure. */ + uio.uio_iov = iov; + uio.uio_iovcnt = 1; + uio.uio_segflg = UIO_SYSSPACE; + uio.uio_resid = len; + + msg.msg_name = name; + msg.msg_namelen = namelen; + + if ((error = sosendmsg(so, &msg, &uio)) == 0) { + /* Data sent */ + if (uio.uio_resid == 0) { + /* All data sent. Success. */ + return (0); + } else { + /* Not all data was sent. Failure */ + return (-1); + } + } + + /* Send failed */ + return (error); +} + +/* + * idm_iov_sosend - Sends an iovec on a connection. + * + * This function puts the data provided on the wire by calling sosendmsg. + * It will return only when all the data has been sent or if an error + * occurs. + * + * Returns 0 for success, the socket errno value if sosendmsg fails, and + * -1 if sosendmsg returns success but uio_resid != 0 + */ +int +idm_iov_sosend(struct sonode *so, iovec_t *iop, int iovlen, size_t total_len) +{ + struct msghdr msg; + struct uio uio; + int error; + + ASSERT(iop != NULL); + + /* Initialization of the message header. */ + bzero(&msg, sizeof (msg)); + msg.msg_iov = iop; + msg.msg_iovlen = iovlen; + + /* Initialization of the uio structure. */ + bzero(&uio, sizeof (uio)); + uio.uio_iov = iop; + uio.uio_iovcnt = iovlen; + uio.uio_segflg = UIO_SYSSPACE; + uio.uio_resid = total_len; + + if ((error = sosendmsg(so, &msg, &uio)) == 0) { + /* Data sent */ + if (uio.uio_resid == 0) { + /* All data sent. Success. */ + return (0); + } else { + /* Not all data was sent. Failure */ + return (-1); + } + } + + /* Send failed */ + return (error); +} + +/* + * idm_iov_sorecv - Receives an iovec from a connection + * + * This function gets the data asked for from the socket. It will return + * only when all the requested data has been retrieved or if an error + * occurs. + * + * Returns 0 for success, the socket errno value if sorecvmsg fails, and + * -1 if sorecvmsg returns success but uio_resid != 0 + */ +int +idm_iov_sorecv(struct sonode *so, iovec_t *iop, int iovlen, size_t total_len) +{ + struct msghdr msg; + struct uio uio; + int error; + + ASSERT(iop != NULL); + + /* Initialization of the message header. */ + bzero(&msg, sizeof (msg)); + msg.msg_iov = iop; + msg.msg_flags = MSG_WAITALL; + msg.msg_iovlen = iovlen; + + /* Initialization of the uio structure. */ + bzero(&uio, sizeof (uio)); + uio.uio_iov = iop; + uio.uio_iovcnt = iovlen; + uio.uio_segflg = UIO_SYSSPACE; + uio.uio_resid = total_len; + + if ((error = sorecvmsg(so, &msg, &uio)) == 0) { + /* Received data */ + if (uio.uio_resid == 0) { + /* All requested data received. Success */ + return (0); + } else { + /* + * Not all data was received. The connection has + * probably failed. + */ + return (-1); + } + } + + /* Receive failed */ + return (error); +} + +static void +idm_set_ini_preconnect_options(idm_so_conn_t *sc) +{ + int conn_abort = 10000; + int conn_notify = 2000; + int abort = 30000; + + /* Pre-connect socket options */ + (void) sosetsockopt(sc->ic_so, IPPROTO_TCP, TCP_CONN_NOTIFY_THRESHOLD, + (char *)&conn_notify, sizeof (int)); + (void) sosetsockopt(sc->ic_so, IPPROTO_TCP, TCP_CONN_ABORT_THRESHOLD, + (char *)&conn_abort, sizeof (int)); + (void) sosetsockopt(sc->ic_so, IPPROTO_TCP, TCP_ABORT_THRESHOLD, + (char *)&abort, sizeof (int)); +} + +static void +idm_set_ini_postconnect_options(idm_so_conn_t *sc) +{ + int32_t rcvbuf = IDM_RCVBUF_SIZE; + int32_t sndbuf = IDM_SNDBUF_SIZE; + const int on = 1; + + /* Set postconnect options */ + (void) sosetsockopt(sc->ic_so, IPPROTO_TCP, TCP_NODELAY, + (char *)&on, sizeof (int)); + (void) sosetsockopt(sc->ic_so, SOL_SOCKET, SO_RCVBUF, + (char *)&rcvbuf, sizeof (int)); + (void) sosetsockopt(sc->ic_so, SOL_SOCKET, SO_SNDBUF, + (char *)&sndbuf, sizeof (int)); +} + +static void +idm_set_tgt_connect_options(struct sonode *sonode) +{ + int32_t rcvbuf = IDM_RCVBUF_SIZE; + int32_t sndbuf = IDM_SNDBUF_SIZE; + const int on = 1; + + /* Set connect options */ + (void) sosetsockopt(sonode, SOL_SOCKET, SO_RCVBUF, + (char *)&rcvbuf, sizeof (int)); + (void) sosetsockopt(sonode, SOL_SOCKET, SO_SNDBUF, + (char *)&sndbuf, sizeof (int)); + (void) sosetsockopt(sonode, IPPROTO_TCP, TCP_NODELAY, + (char *)&on, sizeof (on)); +} + +static uint32_t +n2h24(const uchar_t *ptr) +{ + return ((ptr[0] << 16) | (ptr[1] << 8) | ptr[2]); +} + + +static idm_status_t +idm_sorecvhdr(idm_conn_t *ic, idm_pdu_t *pdu) +{ + iscsi_hdr_t *bhs; + uint32_t hdr_digest_crc; + uint32_t crc_calculated; + void *new_hdr; + int ahslen = 0; + int total_len = 0; + int iovlen = 0; + struct iovec iov[2]; + idm_so_conn_t *so_conn; + int rc; + + so_conn = ic->ic_transport_private; + + /* + * Read BHS + */ + bhs = pdu->isp_hdr; + rc = idm_sorecv(so_conn->ic_so, pdu->isp_hdr, sizeof (iscsi_hdr_t)); + if (rc != IDM_STATUS_SUCCESS) { + return (IDM_STATUS_FAIL); + } + + /* + * Check actual AHS length against the amount available in the buffer + */ + pdu->isp_hdrlen = sizeof (iscsi_hdr_t) + + (bhs->hlength * sizeof (uint32_t)); + pdu->isp_datalen = n2h24(bhs->dlength); + if (bhs->hlength > IDM_SORX_CACHE_AHSLEN) { + /* Allocate a new header segment and change the callback */ + new_hdr = kmem_alloc(pdu->isp_hdrlen, KM_SLEEP); + bcopy(pdu->isp_hdr, new_hdr, sizeof (iscsi_hdr_t)); + pdu->isp_hdr = new_hdr; + pdu->isp_flags |= IDM_PDU_ADDL_HDR; + + /* + * This callback will restore the expected values after + * the RX PDU has been processed. + */ + pdu->isp_callback = idm_sorx_addl_pdu_cb; + } + + /* + * Setup receipt of additional header and header digest (if enabled). + */ + if (bhs->hlength > 0) { + iov[iovlen].iov_base = (caddr_t)(pdu->isp_hdr + 1); + ahslen = pdu->isp_hdrlen - sizeof (iscsi_hdr_t); + iov[iovlen].iov_len = ahslen; + total_len += iov[iovlen].iov_len; + iovlen++; + } + + if (ic->ic_conn_flags & IDM_CONN_HEADER_DIGEST) { + iov[iovlen].iov_base = (caddr_t)&hdr_digest_crc; + iov[iovlen].iov_len = sizeof (hdr_digest_crc); + total_len += iov[iovlen].iov_len; + iovlen++; + } + + if ((iovlen != 0) && + (idm_iov_sorecv(so_conn->ic_so, &iov[0], iovlen, + total_len) != 0)) { + return (IDM_STATUS_FAIL); + } + + /* + * Validate header digest if enabled + */ + if (ic->ic_conn_flags & IDM_CONN_HEADER_DIGEST) { + crc_calculated = idm_crc32c(pdu->isp_hdr, + sizeof (iscsi_hdr_t) + ahslen); + if (crc_calculated != hdr_digest_crc) { + /* Invalid Header Digest */ + return (IDM_STATUS_HEADER_DIGEST); + } + } + + return (0); +} + +/* + * idm_so_ini_conn_create() + * Allocate the sockets transport connection resources. + */ +static idm_status_t +idm_so_ini_conn_create(idm_conn_req_t *cr, idm_conn_t *ic) +{ + struct sonode *so; + idm_so_conn_t *so_conn; + idm_status_t idmrc; + + so = idm_socreate(cr->cr_domain, cr->cr_type, + cr->cr_protocol); + if (so == NULL) { + return (IDM_STATUS_FAIL); + } + + /* Bind the socket if configured to do so */ + if (cr->cr_bound) { + if (sobind(so, &cr->cr_bound_addr.sin, + SIZEOF_SOCKADDR(&cr->cr_bound_addr.sin), 0, 0) != 0) { + idm_sodestroy(so); + return (IDM_STATUS_FAIL); + } + } + + idmrc = idm_so_conn_create_common(ic, so); + if (idmrc != IDM_STATUS_SUCCESS) { + idm_soshutdown(so); + idm_sodestroy(so); + return (IDM_STATUS_FAIL); + } + + so_conn = ic->ic_transport_private; + /* Set up socket options */ + idm_set_ini_preconnect_options(so_conn); + + return (IDM_STATUS_SUCCESS); +} + +/* + * idm_so_ini_conn_destroy() + * Tear down the sockets transport connection resources. + */ +static void +idm_so_ini_conn_destroy(idm_conn_t *ic) +{ + idm_so_conn_destroy_common(ic); +} + +/* + * idm_so_ini_conn_connect() + * Establish the connection referred to by the handle previously allocated via + * idm_so_ini_conn_create(). + */ +static idm_status_t +idm_so_ini_conn_connect(idm_conn_t *ic) +{ + idm_so_conn_t *so_conn; + + so_conn = ic->ic_transport_private; + + if (soconnect(so_conn->ic_so, &ic->ic_ini_dst_addr.sin, + (SIZEOF_SOCKADDR(&ic->ic_ini_dst_addr.sin)), 0, 0) != 0) { + idm_soshutdown(so_conn->ic_so); + return (IDM_STATUS_FAIL); + } + + idm_so_conn_connect_common(ic); + + idm_set_ini_postconnect_options(so_conn); + + return (IDM_STATUS_SUCCESS); +} + +idm_status_t +idm_so_tgt_conn_create(idm_conn_t *ic, struct sonode *new_so) +{ + idm_status_t idmrc; + + idmrc = idm_so_conn_create_common(ic, new_so); + + return (idmrc); +} + +static void +idm_so_tgt_conn_destroy(idm_conn_t *ic) +{ + idm_so_conn_destroy_common(ic); +} + +/* + * idm_so_tgt_conn_connect() + * Establish the connection in ic, passed from idm_tgt_conn_finish(), which + * is invoked from the SM as a result of an inbound connection request. + */ +static idm_status_t +idm_so_tgt_conn_connect(idm_conn_t *ic) +{ + idm_so_conn_connect_common(ic); + + return (IDM_STATUS_SUCCESS); +} + +static idm_status_t +idm_so_conn_create_common(idm_conn_t *ic, struct sonode *new_so) +{ + idm_so_conn_t *so_conn; + + so_conn = kmem_zalloc(sizeof (idm_so_conn_t), KM_SLEEP); + so_conn->ic_so = new_so; + + ic->ic_transport_private = so_conn; + ic->ic_transport_hdrlen = 0; + + /* Set the scoreboarding flag on this connection */ + ic->ic_conn_flags |= IDM_CONN_USE_SCOREBOARD; + + /* + * Initialize tx thread mutex and list + */ + mutex_init(&so_conn->ic_tx_mutex, NULL, MUTEX_DEFAULT, NULL); + cv_init(&so_conn->ic_tx_cv, NULL, CV_DEFAULT, NULL); + list_create(&so_conn->ic_tx_list, sizeof (idm_pdu_t), + offsetof(idm_pdu_t, idm_tx_link)); + + return (IDM_STATUS_SUCCESS); +} + +static void +idm_so_conn_destroy_common(idm_conn_t *ic) +{ + idm_so_conn_t *so_conn = ic->ic_transport_private; + + ic->ic_transport_private = NULL; + idm_sodestroy(so_conn->ic_so); + list_destroy(&so_conn->ic_tx_list); + mutex_destroy(&so_conn->ic_tx_mutex); + cv_destroy(&so_conn->ic_tx_cv); + + kmem_free(so_conn, sizeof (idm_so_conn_t)); +} + +static void +idm_so_conn_connect_common(idm_conn_t *ic) +{ + idm_so_conn_t *so_conn; + + so_conn = ic->ic_transport_private; + + SOP_GETSOCKNAME(so_conn->ic_so); + + /* Set the local and remote addresses in the idm conn handle */ + mutex_enter(&so_conn->ic_so->so_lock); + bcopy(so_conn->ic_so->so_laddr_sa, &ic->ic_laddr, + so_conn->ic_so->so_laddr_len); + bcopy(so_conn->ic_so->so_faddr_sa, &ic->ic_raddr, + so_conn->ic_so->so_faddr_len); + mutex_exit(&so_conn->ic_so->so_lock); + + mutex_enter(&ic->ic_mutex); + so_conn->ic_tx_thread = thread_create(NULL, 0, idm_sotx_thread, ic, 0, + &p0, TS_RUN, minclsyspri); + so_conn->ic_rx_thread = thread_create(NULL, 0, idm_sorx_thread, ic, 0, + &p0, TS_RUN, minclsyspri); + + while (!so_conn->ic_rx_thread_running || !so_conn->ic_tx_thread_running) + cv_wait(&ic->ic_cv, &ic->ic_mutex); + mutex_exit(&ic->ic_mutex); +} + +/* + * idm_so_conn_disconnect() + * Shutdown the socket connection and stop the thread + */ +static void +idm_so_conn_disconnect(idm_conn_t *ic) +{ + idm_so_conn_t *so_conn; + + so_conn = ic->ic_transport_private; + + mutex_enter(&ic->ic_mutex); + so_conn->ic_rx_thread_running = B_FALSE; + so_conn->ic_tx_thread_running = B_FALSE; + /* We need to wakeup the TX thread */ + mutex_enter(&so_conn->ic_tx_mutex); + cv_signal(&so_conn->ic_tx_cv); + mutex_exit(&so_conn->ic_tx_mutex); + mutex_exit(&ic->ic_mutex); + + /* This should wakeup the RX thread if it is sleeping */ + idm_soshutdown(so_conn->ic_so); + + thread_join(so_conn->ic_tx_thread_did); + thread_join(so_conn->ic_rx_thread_did); +} + +/* + * idm_so_tgt_svc_create() + * Establish a service on an IP address and port. idm_svc_req_t contains + * the service parameters. + */ +/*ARGSUSED*/ +static idm_status_t +idm_so_tgt_svc_create(idm_svc_req_t *sr, idm_svc_t *is) +{ + idm_so_svc_t *so_svc; + + so_svc = kmem_zalloc(sizeof (idm_so_svc_t), KM_SLEEP); + + /* Set the new sockets service in svc handle */ + is->is_so_svc = (void *)so_svc; + + return (IDM_STATUS_SUCCESS); +} + +/* + * idm_so_tgt_svc_destroy() + * Teardown sockets resources allocated in idm_so_tgt_svc_create() + */ +static void +idm_so_tgt_svc_destroy(idm_svc_t *is) +{ + /* the socket will have been torn down; free the service */ + kmem_free(is->is_so_svc, sizeof (idm_so_svc_t)); +} + +/* + * idm_so_tgt_svc_online() + * Launch a watch thread on the svc allocated in idm_so_tgt_svc_create() + */ + +static idm_status_t +idm_so_tgt_svc_online(idm_svc_t *is) +{ + idm_so_svc_t *so_svc; + idm_svc_req_t *sr = &is->is_svc_req; + struct sockaddr_in6 sin6_ip; + const uint32_t on = 1; + const uint32_t off = 0; + + mutex_enter(&is->is_mutex); + so_svc = (idm_so_svc_t *)is->is_so_svc; + + /* + * Try creating an IPv6 socket first + */ + if ((so_svc->is_so = idm_socreate(PF_INET6, SOCK_STREAM, 0)) == NULL) { + mutex_exit(&is->is_mutex); + return (IDM_STATUS_FAIL); + } else { + bzero(&sin6_ip, sizeof (sin6_ip)); + sin6_ip.sin6_family = AF_INET6; + sin6_ip.sin6_port = htons(sr->sr_port); + sin6_ip.sin6_addr = in6addr_any; + + (void) sosetsockopt(so_svc->is_so, SOL_SOCKET, SO_REUSEADDR, + (char *)&on, sizeof (on)); + /* + * Turn off SO_MAC_EXEMPT so future sobinds succeed + */ + (void) sosetsockopt(so_svc->is_so, SOL_SOCKET, SO_MAC_EXEMPT, + (char *)&off, sizeof (off)); + + if (sobind(so_svc->is_so, (struct sockaddr *)&sin6_ip, + sizeof (sin6_ip), 0, 0) != 0) { + mutex_exit(&is->is_mutex); + idm_sodestroy(so_svc->is_so); + return (IDM_STATUS_FAIL); + } + } + + idm_set_tgt_connect_options(so_svc->is_so); + + if (solisten(so_svc->is_so, 5) != 0) { + mutex_exit(&is->is_mutex); + idm_soshutdown(so_svc->is_so); + idm_sodestroy(so_svc->is_so); + return (IDM_STATUS_FAIL); + } + + /* Launch a watch thread */ + so_svc->is_thread = thread_create(NULL, 0, idm_so_svc_port_watcher, + is, 0, &p0, TS_RUN, minclsyspri); + + if (so_svc->is_thread == NULL) { + /* Failure to launch; teardown the socket */ + mutex_exit(&is->is_mutex); + idm_soshutdown(so_svc->is_so); + idm_sodestroy(so_svc->is_so); + return (IDM_STATUS_FAIL); + } + + /* Wait for the port watcher thread to start */ + while (!so_svc->is_thread_running) + cv_wait(&is->is_cv, &is->is_mutex); + mutex_exit(&is->is_mutex); + + return (IDM_STATUS_SUCCESS); +} + +/* + * idm_so_tgt_svc_offline + * + * Stop listening on the IP address and port identified by idm_svc_t. + */ +static void +idm_so_tgt_svc_offline(idm_svc_t *is) +{ + idm_so_svc_t *so_svc; + + mutex_enter(&is->is_mutex); + so_svc = (idm_so_svc_t *)is->is_so_svc; + so_svc->is_thread_running = B_FALSE; + mutex_exit(&is->is_mutex); + + /* + * When called from the kernel, soaccept blocks and cannot be woken + * up via the sockfs API. soclose does not work like you would + * hope. When the Volo project is available we can switch to that + * API which should address this issue. For now, we will poke at + * the socket to wake it up. + */ + mutex_enter(&so_svc->is_so->so_lock); + so_svc->is_so->so_error = EINTR; + cv_signal(&so_svc->is_so->so_connind_cv); + mutex_exit(&so_svc->is_so->so_lock); + + /* + * Now we expect the port watcher thread to terminate + */ + thread_join(so_svc->is_thread_did); + + /* + * Teardown socket + */ + idm_sodestroy(so_svc->is_so); +} + +/* + * Watch thread for target service connection establishment. + */ +void +idm_so_svc_port_watcher(void *arg) +{ + idm_svc_t *svc = arg; + struct sonode *new_so; + idm_conn_t *ic; + idm_status_t idmrc; + idm_so_svc_t *so_svc; + int rc; + const uint32_t off = 0; + + mutex_enter(&svc->is_mutex); + + so_svc = svc->is_so_svc; + so_svc->is_thread_running = B_TRUE; + so_svc->is_thread_did = so_svc->is_thread->t_did; + + cv_signal(&svc->is_cv); + + IDM_SVC_LOG(CE_NOTE, "iSCSI service (%p/%d) online", (void *)svc, + svc->is_svc_req.sr_port); + + while (so_svc->is_thread_running) { + mutex_exit(&svc->is_mutex); + + if ((rc = soaccept(so_svc->is_so, 0, &new_so)) != 0) { + mutex_enter(&svc->is_mutex); + if (rc == ECONNABORTED) + continue; + /* Connection problem */ + break; + } + /* + * Turn off SO_MAC_EXEMPT so future sobinds succeed + */ + (void) sosetsockopt(new_so, SOL_SOCKET, SO_MAC_EXEMPT, + (char *)&off, sizeof (off)); + + idmrc = idm_svc_conn_create(svc, IDM_TRANSPORT_TYPE_SOCKETS, + &ic); + if (idmrc != IDM_STATUS_SUCCESS) { + /* Drop connection */ + idm_soshutdown(new_so); + idm_sodestroy(new_so); + mutex_enter(&svc->is_mutex); + continue; + } + + idmrc = idm_so_tgt_conn_create(ic, new_so); + if (idmrc != IDM_STATUS_SUCCESS) { + idm_svc_conn_destroy(ic); + idm_soshutdown(new_so); + idm_sodestroy(new_so); + mutex_enter(&svc->is_mutex); + continue; + } + + /* + * Kick the state machine. At CS_S3_XPT_UP the state machine + * will notify the client (target) about the new connection. + */ + idm_conn_event(ic, CE_CONNECT_ACCEPT, NULL); + + mutex_enter(&svc->is_mutex); + } + + so_svc->is_thread_running = B_FALSE; + mutex_exit(&svc->is_mutex); + + IDM_SVC_LOG(CE_NOTE, "iSCSI service (%p/%d) offline", (void *)svc, + svc->is_svc_req.sr_port); + + thread_exit(); +} + +/* + * idm_so_free_task_rsrc() stops any ongoing processing of the task and + * frees resources associated with the task. + * + * It's not clear that this should return idm_status_t. What do we do + * if it fails? + */ +static idm_status_t +idm_so_free_task_rsrc(idm_task_t *idt) +{ + idm_buf_t *idb; + + /* + * If this is a target connection, call idm_buf_rx_from_ini_done for + * any buffer on the "outbufv" list with idb->idb_in_transport==B_TRUE. + * + * In addition, remove any buffers associated with this task from + * the ic_tx_list. We'll do this by walking the idt_inbufv list, but + * items don't actually get removed from that list (and completion + * routines called) until idm_task_cleanup. + */ + mutex_enter(&idt->idt_mutex); + + for (idb = list_head(&idt->idt_outbufv); idb != NULL; + idb = list_next(&idt->idt_outbufv, idb)) { + if (idb->idb_in_transport) { + /* + * idm_buf_rx_from_ini_done releases idt->idt_mutex + */ + idm_buf_rx_from_ini_done(idt, idb, IDM_STATUS_ABORTED); + mutex_enter(&idt->idt_mutex); + } + } + + for (idb = list_head(&idt->idt_inbufv); idb != NULL; + idb = list_next(&idt->idt_inbufv, idb)) { + /* + * We want to remove these items from the tx_list as well, + * but knowing it's in the idt_inbufv list is not a guarantee + * that it's in the tx_list. If it's on the tx list then + * let idm_sotx_thread() clean it up. + */ + if (idb->idb_in_transport && !idb->idb_tx_thread) { + /* + * idm_buf_tx_to_ini_done releases idt->idt_mutex + */ + idm_buf_tx_to_ini_done(idt, idb, IDM_STATUS_ABORTED); + mutex_enter(&idt->idt_mutex); + } + } + + mutex_exit(&idt->idt_mutex); + + return (IDM_STATUS_SUCCESS); +} + +/* + * idm_so_negotiate_key_values() validates the key values for this connection + */ +/* ARGSUSED */ +static kv_status_t +idm_so_negotiate_key_values(idm_conn_t *it, nvlist_t *request_nvl, + nvlist_t *response_nvl, nvlist_t *negotiated_nvl) +{ + /* All parameters are negotiated at the iscsit level */ + return (KV_HANDLED); +} + +/* + * idm_so_notice_key_values() activates the negotiated key values for + * this connection. + */ +static idm_status_t +idm_so_notice_key_values(idm_conn_t *it, nvlist_t *negotiated_nvl) +{ + char *nvp_name; + nvpair_t *nvp; + nvpair_t *next_nvp; + int nvrc; + idm_status_t idm_status; + const idm_kv_xlate_t *ikvx; + + for (nvp = nvlist_next_nvpair(negotiated_nvl, NULL); + nvp != NULL; nvp = next_nvp) { + next_nvp = nvlist_next_nvpair(negotiated_nvl, nvp); + nvp_name = nvpair_name(nvp); + + ikvx = idm_lookup_kv_xlate(nvp_name, strlen(nvp_name)); + switch (ikvx->ik_key_id) { + case KI_HEADER_DIGEST: + case KI_DATA_DIGEST: + idm_status = idm_so_handle_digest(it, nvp, ikvx); + ASSERT(idm_status == 0); + + /* Remove processed item from negotiated_nvl list */ + nvrc = nvlist_remove_all( + negotiated_nvl, ikvx->ik_key_name); + ASSERT(nvrc == 0); + break; + default: + break; + } + } + return (IDM_STATUS_SUCCESS); +} + + +static idm_status_t +idm_so_handle_digest(idm_conn_t *it, nvpair_t *digest_choice, + const idm_kv_xlate_t *ikvx) +{ + int nvrc; + char *digest_choice_string; + + nvrc = nvpair_value_string(digest_choice, + &digest_choice_string); + ASSERT(nvrc == 0); + if (strcasecmp(digest_choice_string, "crc32c") == 0) { + switch (ikvx->ik_key_id) { + case KI_HEADER_DIGEST: + it->ic_conn_flags |= IDM_CONN_HEADER_DIGEST; + break; + case KI_DATA_DIGEST: + it->ic_conn_flags |= IDM_CONN_DATA_DIGEST; + break; + default: + ASSERT(0); + break; + } + } else if (strcasecmp(digest_choice_string, "none") == 0) { + switch (ikvx->ik_key_id) { + case KI_HEADER_DIGEST: + it->ic_conn_flags &= ~IDM_CONN_HEADER_DIGEST; + break; + case KI_DATA_DIGEST: + it->ic_conn_flags &= ~IDM_CONN_DATA_DIGEST; + break; + default: + ASSERT(0); + break; + } + } else { + ASSERT(0); + } + + return (IDM_STATUS_SUCCESS); +} + + +/* + * idm_so_conn_is_capable() verifies that the passed connection is provided + * for by the sockets interface. + */ +/* ARGSUSED */ +static boolean_t +idm_so_conn_is_capable(idm_conn_req_t *ic, idm_transport_caps_t *caps) +{ + return (B_TRUE); +} + +/* + * idm_so_rx_datain() validates the Data Sequence number of the PDU. The + * idm_sorecv_scsidata() function invoked earlier actually reads the data + * off the socket into the appropriate buffers. + */ +static void +idm_so_rx_datain(idm_conn_t *ic, idm_pdu_t *pdu) +{ + iscsi_data_hdr_t *bhs; + idm_task_t *idt; + idm_buf_t *idb; + uint32_t datasn; + size_t offset; + iscsi_hdr_t *ihp = (iscsi_hdr_t *)pdu->isp_hdr; + iscsi_data_rsp_hdr_t *idrhp = (iscsi_data_rsp_hdr_t *)ihp; + + ASSERT(ic != NULL); + ASSERT(pdu != NULL); + + bhs = (iscsi_data_hdr_t *)pdu->isp_hdr; + datasn = ntohl(bhs->datasn); + offset = ntohl(bhs->offset); + + ASSERT(bhs->opcode == ISCSI_OP_SCSI_DATA_RSP); + + /* + * Look up the task corresponding to the initiator task tag + * to get the buffers affiliated with the task. + */ + idt = idm_task_find(ic, bhs->itt, bhs->ttt); + if (idt == NULL) { + IDM_CONN_LOG(CE_WARN, "idm_so_rx_datain: failed to find task"); + idm_pdu_rx_protocol_error(ic, pdu); + return; + } + + idb = pdu->isp_sorx_buf; + if (idb == NULL) { + IDM_CONN_LOG(CE_WARN, + "idm_so_rx_datain: failed to find buffer"); + idm_task_rele(idt); + idm_pdu_rx_protocol_error(ic, pdu); + return; + } + + /* + * DataSN values should be sequential and should not have any gaps or + * repetitions. Check the DataSN with the one stored in the task. + */ + if (datasn == idt->idt_exp_datasn) { + idt->idt_exp_datasn++; /* keep track of DataSN received */ + } else { + IDM_CONN_LOG(CE_WARN, "idm_so_rx_datain: datasn out of order"); + idm_task_rele(idt); + idm_pdu_rx_protocol_error(ic, pdu); + return; + } + idm_task_rele(idt); + + /* + * PDUs in a sequence should be in continuously increasing + * address offset + */ + if (offset != idb->idb_exp_offset) { + IDM_CONN_LOG(CE_WARN, "idm_so_rx_datain: unexpected offset"); + idm_pdu_rx_protocol_error(ic, pdu); + return; + } + /* Expected next relative buffer offset */ + idb->idb_exp_offset += n2h24(bhs->dlength); + + /* + * For now call scsi_rsp which will process the data rsp + * Revisit, need to provide an explicit client entry point for + * phase collapse completions. + */ + if (((ihp->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_SCSI_DATA_RSP) && + (idrhp->flags & ISCSI_FLAG_DATA_STATUS)) { + (*ic->ic_conn_ops.icb_rx_scsi_rsp)(ic, pdu); + } + + idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); +} + +/* + * The idm_so_rx_dataout() function is used by the iSCSI target to read + * data from the Data-Out PDU sent by the iSCSI initiator. + * + * This function gets the Initiator Task Tag from the PDU BHS and looks up the + * task to get the buffers associated with the PDU. A PDU might span buffers. + * The data is then read into the respective buffer. + */ +static void +idm_so_rx_dataout(idm_conn_t *ic, idm_pdu_t *pdu) +{ + + iscsi_data_hdr_t *bhs; + idm_task_t *idt; + idm_buf_t *idb; + size_t offset; + + ASSERT(ic != NULL); + ASSERT(pdu != NULL); + + bhs = (iscsi_data_hdr_t *)pdu->isp_hdr; + offset = ntohl(bhs->offset); + ASSERT(bhs->opcode == ISCSI_OP_SCSI_DATA); + + /* + * Look up the task corresponding to the initiator task tag + * to get the buffers affiliated with the task. + */ + idt = idm_task_find(ic, bhs->itt, bhs->ttt); + if (idt == NULL) { + IDM_CONN_LOG(CE_WARN, + "idm_so_rx_dataout: failed to find task"); + idm_pdu_rx_protocol_error(ic, pdu); + return; + } + + idb = pdu->isp_sorx_buf; + if (idb == NULL) { + IDM_CONN_LOG(CE_WARN, + "idm_so_rx_dataout: failed to find buffer"); + idm_task_rele(idt); + idm_pdu_rx_protocol_error(ic, pdu); + return; + } + + /* Keep track of data transferred - check data offsets */ + if (offset != idb->idb_exp_offset) { + IDM_CONN_LOG(CE_NOTE, "idm_so_rx_dataout: offset out of seq: " + "%ld, %d", offset, idb->idb_exp_offset); + idm_task_rele(idt); + idm_pdu_rx_protocol_error(ic, pdu); + return; + } + /* Expected next relative offset */ + idb->idb_exp_offset += ntoh24(bhs->dlength); + + /* + * Call the buffer callback when the transfer is complete + * + * The connection state machine should only abort tasks after + * shutting down the connection so we are assured that there + * won't be a simultaneous attempt to abort this task at the + * same time as we are processing this PDU (due to a connection + * state change). + */ + if (bhs->flags & ISCSI_FLAG_FINAL) { + /* + * We only want to call idm_buf_rx_from_ini_done once + * per transfer. It's possible that this task has + * already been aborted in which case + * idm_so_free_task_rsrc will call idm_buf_rx_from_ini_done + * for each buffer with idb_in_transport==B_TRUE. To + * close this window and ensure that this doesn't happen, + * we'll clear idb->idb_in_transport now while holding + * the task mutex. This is only really an issue for + * SCSI task abort -- if tasks were being aborted because + * of a connection state change the state machine would + * have already stopped the receive thread. + */ + mutex_enter(&idt->idt_mutex); + + /* + * Release the task hold here (obtained in idm_task_find) + * because the task may complete synchronously during + * idm_buf_rx_from_ini_done. Since we still have an active + * buffer we know there is at least one additional hold on idt. + */ + idm_task_rele(idt); + + /* + * idm_buf_rx_from_ini_done releases idt->idt_mutex + */ + idm_buf_rx_from_ini_done(idt, idb, IDM_STATUS_SUCCESS); + idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); + return; + } + + idm_task_rele(idt); + idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); +} + +/* + * The idm_so_rx_rtt() function is used by the iSCSI initiator to handle + * the R2T PDU sent by the iSCSI target indicating that it is ready to + * accept data. This gets the Initiator Task Tag (itt) from the PDU BHS + * and looks up the task in the task tree using the itt to get the output + * buffers associated the task. The R2T PDU contains the offset of the + * requested data and the data length. This function then constructs a + * sequence of iSCSI PDUs and outputs the requested data. Each Data-Out + * PDU is associated with the R2T by the Target Transfer Tag (ttt). + */ +static void +idm_so_rx_rtt(idm_conn_t *ic, idm_pdu_t *pdu) +{ + idm_task_t *idt; + idm_buf_t *idb; + iscsi_rtt_hdr_t *rtt_hdr; + uint32_t data_offset; + + ASSERT(ic != NULL); + ASSERT(pdu != NULL); + + rtt_hdr = (iscsi_rtt_hdr_t *)pdu->isp_hdr; + data_offset = ntohl(rtt_hdr->data_offset); + + idt = idm_task_find(ic, rtt_hdr->itt, rtt_hdr->ttt); + + if (idt == NULL) { + IDM_CONN_LOG(CE_WARN, "idm_so_rx_rtt: could not find task"); + idm_pdu_rx_protocol_error(ic, pdu); + return; + } + + /* Find the buffer bound to the task by the iSCSI initiator */ + mutex_enter(&idt->idt_mutex); + idb = idm_buf_find(&idt->idt_outbufv, data_offset); + idt->idt_r2t_ttt = rtt_hdr->ttt; + /* reset to zero */ + idt->idt_exp_datasn = 0; + if (idb == NULL) { + mutex_exit(&idt->idt_mutex); + idm_task_rele(idt); + IDM_CONN_LOG(CE_WARN, "idm_so_rx_rtt: could not find buffer"); + idm_pdu_rx_protocol_error(ic, pdu); + return; + } + + (void) idm_so_send_buf_region(idt, ISCSI_OP_SCSI_DATA, idb, + data_offset, ntohl(rtt_hdr->data_length)); + mutex_exit(&idt->idt_mutex); + + idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); + idm_task_rele(idt); + +} + +idm_status_t +idm_sorecvdata(idm_conn_t *ic, idm_pdu_t *pdu) +{ + uint8_t pad[ISCSI_PAD_WORD_LEN]; + int pad_len; + uint32_t data_digest_crc; + uint32_t crc_calculated; + int total_len; + idm_so_conn_t *so_conn; + + so_conn = ic->ic_transport_private; + + pad_len = ((ISCSI_PAD_WORD_LEN - + (pdu->isp_datalen & (ISCSI_PAD_WORD_LEN - 1))) & + (ISCSI_PAD_WORD_LEN - 1)); + + ASSERT(pdu->isp_iovlen < (PDU_MAX_IOVLEN - 2)); /* pad + data digest */ + + total_len = pdu->isp_datalen; + + if (pad_len) { + pdu->isp_iov[pdu->isp_iovlen].iov_base = (char *)&pad; + pdu->isp_iov[pdu->isp_iovlen].iov_len = pad_len; + total_len += pad_len; + pdu->isp_iovlen++; + } + + /* setup data digest */ + if ((ic->ic_conn_flags & IDM_CONN_DATA_DIGEST) != 0) { + pdu->isp_iov[pdu->isp_iovlen].iov_base = + (char *)&data_digest_crc; + pdu->isp_iov[pdu->isp_iovlen].iov_len = + sizeof (data_digest_crc); + total_len += sizeof (data_digest_crc); + pdu->isp_iovlen++; + } + + if (idm_iov_sorecv(so_conn->ic_so, &pdu->isp_iov[0], + pdu->isp_iovlen, total_len) != 0) { + return (IDM_STATUS_IO); + } + + if ((ic->ic_conn_flags & IDM_CONN_DATA_DIGEST) != 0) { + crc_calculated = idm_crc32c(pdu->isp_data, + pdu->isp_datalen); + if (pad_len) { + crc_calculated = idm_crc32c_continued((char *)&pad, + pad_len, crc_calculated); + } + if (crc_calculated != data_digest_crc) { + IDM_CONN_LOG(CE_WARN, + "idm_sorecvdata: " + "CRC error: actual 0x%x, calc 0x%x", + data_digest_crc, crc_calculated); + + /* Invalid Data Digest */ + return (IDM_STATUS_DATA_DIGEST); + } + } + + return (IDM_STATUS_SUCCESS); +} + +/* + * idm_sorecv_scsidata() is used to receive scsi data from the socket. The + * Data-type PDU header must be read into the idm_pdu_t structure prior to + * calling this function. + */ +idm_status_t +idm_sorecv_scsidata(idm_conn_t *ic, idm_pdu_t *pdu) +{ + iscsi_data_hdr_t *bhs; + idm_task_t *task; + uint32_t offset; + uint8_t opcode; + uint32_t dlength; + list_t *buflst; + uint32_t xfer_bytes; + idm_status_t status; + + ASSERT(ic != NULL); + ASSERT(pdu != NULL); + + bhs = (iscsi_data_hdr_t *)pdu->isp_hdr; + + offset = ntohl(bhs->offset); + opcode = bhs->opcode; + dlength = n2h24(bhs->dlength); + + ASSERT((opcode == ISCSI_OP_SCSI_DATA_RSP) || + (opcode == ISCSI_OP_SCSI_DATA)); + + /* + * Successful lookup implicitly gets a "hold" on the task. This + * hold must be released before leaving this function. At one + * point we were caching this task context and retaining the hold + * but it turned out to be very difficult to release the hold properly. + * The task can be aborted and the connection shutdown between this + * call and the subsequent expected call to idm_so_rx_datain/ + * idm_so_rx_dataout (in which case those functions are not called). + * Releasing the hold in the PDU callback doesn't work well either + * because the whole task may be completed by then at which point + * it is too late to release the hold -- for better or worse this + * code doesn't wait on the refcnts during normal operation. + * idm_task_find() is very fast and it is not a huge burden if we + * have to do it twice. + */ + task = idm_task_find(ic, bhs->itt, bhs->ttt); + if (task == NULL) { + IDM_CONN_LOG(CE_WARN, + "idm_sorecv_scsidata: could not find task"); + return (IDM_STATUS_FAIL); + } + + mutex_enter(&task->idt_mutex); + buflst = (opcode == ISCSI_OP_SCSI_DATA_RSP) ? + &task->idt_inbufv : &task->idt_outbufv; + pdu->isp_sorx_buf = idm_buf_find(buflst, offset); + mutex_exit(&task->idt_mutex); + + if (pdu->isp_sorx_buf == NULL) { + idm_task_rele(task); + IDM_CONN_LOG(CE_WARN, "idm_sorecv_scsidata: could not find " + "buffer for offset %x opcode=%x", + offset, opcode); + return (IDM_STATUS_FAIL); + } + + xfer_bytes = idm_fill_iov(pdu, pdu->isp_sorx_buf, offset, dlength); + ASSERT(xfer_bytes != 0); + if (xfer_bytes != dlength) { + idm_task_rele(task); + /* + * Buffer overflow, connection error. The PDU data is still + * sitting in the socket so we can't use the connection + * again until that data is drained. + */ + return (IDM_STATUS_FAIL); + } + + status = idm_sorecvdata(ic, pdu); + + idm_task_rele(task); + + return (status); +} + +static uint32_t +idm_fill_iov(idm_pdu_t *pdu, idm_buf_t *idb, uint32_t ro, uint32_t dlength) +{ + uint32_t buf_ro = ro - idb->idb_bufoffset; + uint32_t xfer_len = min(dlength, idb->idb_buflen - buf_ro); + + ASSERT(ro >= idb->idb_bufoffset); + + pdu->isp_iov[pdu->isp_iovlen].iov_base = + (caddr_t)idb->idb_buf + buf_ro; + pdu->isp_iov[pdu->isp_iovlen].iov_len = xfer_len; + pdu->isp_iovlen++; + + return (xfer_len); +} + +int +idm_sorecv_nonscsidata(idm_conn_t *ic, idm_pdu_t *pdu) +{ + pdu->isp_data = kmem_alloc(pdu->isp_datalen, KM_SLEEP); + ASSERT(pdu->isp_data != NULL); + + pdu->isp_databuflen = pdu->isp_datalen; + pdu->isp_iov[0].iov_base = (caddr_t)pdu->isp_data; + pdu->isp_iov[0].iov_len = pdu->isp_datalen; + pdu->isp_iovlen = 1; + /* + * Since we are associating a new data buffer with this received + * PDU we need to set a specific callback to free the data + * after the PDU is processed. + */ + pdu->isp_flags |= IDM_PDU_ADDL_DATA; + pdu->isp_callback = idm_sorx_addl_pdu_cb; + + return (idm_sorecvdata(ic, pdu)); +} + +void +idm_sorx_thread(void *arg) +{ + boolean_t conn_failure = B_FALSE; + idm_conn_t *ic = (idm_conn_t *)arg; + idm_so_conn_t *so_conn; + idm_pdu_t *pdu; + idm_status_t rc; + + idm_conn_hold(ic); + + mutex_enter(&ic->ic_mutex); + + so_conn = ic->ic_transport_private; + so_conn->ic_rx_thread_running = B_TRUE; + so_conn->ic_rx_thread_did = so_conn->ic_rx_thread->t_did; + cv_signal(&ic->ic_cv); + + while (so_conn->ic_rx_thread_running) { + mutex_exit(&ic->ic_mutex); + + /* + * Get PDU with default header size (large enough for + * BHS plus any anticipated AHS). PDU from + * the cache will have all values set correctly + * for sockets RX including callback. + */ + pdu = kmem_cache_alloc(idm.idm_sorx_pdu_cache, KM_SLEEP); + pdu->isp_ic = ic; + pdu->isp_flags = 0; + pdu->isp_transport_hdrlen = 0; + + if ((rc = idm_sorecvhdr(ic, pdu)) != 0) { + /* + * Call idm_pdu_complete so that we call the callback + * and ensure any memory allocated in idm_sorecvhdr + * gets freed up. + */ + idm_pdu_complete(pdu, IDM_STATUS_FAIL); + + /* + * If ic_rx_thread_running is still set then + * this is some kind of connection problem + * on the socket. In this case we want to + * generate an event. Otherwise some other + * thread closed the socket due to another + * issue in which case we don't need to + * generate an event. + */ + mutex_enter(&ic->ic_mutex); + if (so_conn->ic_rx_thread_running) { + conn_failure = B_TRUE; + so_conn->ic_rx_thread_running = B_FALSE; + } + + continue; + } + + /* + * Header has been read and validated. Now we need + * to read the PDU data payload (if present). SCSI data + * need to be transferred from the socket directly into + * the associated transfer buffer for the SCSI task. + */ + if (pdu->isp_datalen != 0) { + if ((IDM_PDU_OPCODE(pdu) == ISCSI_OP_SCSI_DATA) || + (IDM_PDU_OPCODE(pdu) == ISCSI_OP_SCSI_DATA_RSP)) { + rc = idm_sorecv_scsidata(ic, pdu); + /* + * All SCSI errors are fatal to the + * connection right now since we have no + * place to put the data. What we need + * is some kind of sink to dispose of unwanted + * SCSI data. For example an invalid task tag + * should not kill the connection (although + * we may want to drop the connection). + */ + } else { + /* + * Not data PDUs so allocate a buffer for the + * data segment and read the remaining data. + */ + rc = idm_sorecv_nonscsidata(ic, pdu); + } + if (rc != 0) { + /* + * Call idm_pdu_complete so that we call the + * callback and ensure any memory allocated + * in idm_sorecvhdr gets freed up. + */ + idm_pdu_complete(pdu, IDM_STATUS_FAIL); + + /* + * If ic_rx_thread_running is still set then + * this is some kind of connection problem + * on the socket. In this case we want to + * generate an event. Otherwise some other + * thread closed the socket due to another + * issue in which case we don't need to + * generate an event. + */ + mutex_enter(&ic->ic_mutex); + if (so_conn->ic_rx_thread_running) { + conn_failure = B_TRUE; + so_conn->ic_rx_thread_running = B_FALSE; + } + continue; + } + } + + /* + * Process RX PDU + */ + idm_pdu_rx(ic, pdu); + + mutex_enter(&ic->ic_mutex); + } + + mutex_exit(&ic->ic_mutex); + + /* + * If we dropped out of the RX processing loop because of + * a socket problem or other connection failure (including + * digest errors) then we need to generate a state machine + * event to shut the connection down. + * If the state machine is already in, for example, INIT_ERROR, this + * event will get dropped, and the TX thread will never be notified + * to shut down. To be safe, we'll just notify it here. + */ + if (conn_failure) { + if (so_conn->ic_tx_thread_running) { + so_conn->ic_tx_thread_running = B_FALSE; + mutex_enter(&so_conn->ic_tx_mutex); + cv_signal(&so_conn->ic_tx_cv); + mutex_exit(&so_conn->ic_tx_mutex); + } + + idm_conn_event(ic, CE_TRANSPORT_FAIL, rc); + } + + idm_conn_rele(ic); + + thread_exit(); +} + +/* + * idm_so_tx + * + * This is the implementation of idm_transport_ops_t's it_tx_pdu entry + * point. By definition, it is supposed to be fast. So, simply queue + * the entry and return. The real work is done by idm_i_so_tx() via + * idm_sotx_thread(). + */ + +static void +idm_so_tx(idm_conn_t *ic, idm_pdu_t *pdu) +{ + idm_so_conn_t *so_conn = ic->ic_transport_private; + + ASSERT(pdu->isp_ic == ic); + mutex_enter(&so_conn->ic_tx_mutex); + + if (!so_conn->ic_tx_thread_running) { + mutex_exit(&so_conn->ic_tx_mutex); + idm_pdu_complete(pdu, IDM_STATUS_ABORTED); + return; + } + + list_insert_tail(&so_conn->ic_tx_list, (void *)pdu); + cv_signal(&so_conn->ic_tx_cv); + mutex_exit(&so_conn->ic_tx_mutex); +} + +static idm_status_t +idm_i_so_tx(idm_pdu_t *pdu) +{ + idm_conn_t *ic = pdu->isp_ic; + idm_status_t status = IDM_STATUS_SUCCESS; + uint8_t pad[ISCSI_PAD_WORD_LEN]; + int pad_len; + uint32_t hdr_digest_crc; + uint32_t data_digest_crc = 0; + int total_len = 0; + int iovlen = 0; + struct iovec iov[6]; + idm_so_conn_t *so_conn; + + so_conn = ic->ic_transport_private; + + /* Setup BHS */ + iov[iovlen].iov_base = (caddr_t)pdu->isp_hdr; + iov[iovlen].iov_len = pdu->isp_hdrlen; + total_len += iov[iovlen].iov_len; + iovlen++; + + /* Setup header digest */ + if (((pdu->isp_flags & IDM_PDU_LOGIN_TX) == 0) && + (ic->ic_conn_flags & IDM_CONN_HEADER_DIGEST)) { + hdr_digest_crc = idm_crc32c(pdu->isp_hdr, pdu->isp_hdrlen); + + iov[iovlen].iov_base = (caddr_t)&hdr_digest_crc; + iov[iovlen].iov_len = sizeof (hdr_digest_crc); + total_len += iov[iovlen].iov_len; + iovlen++; + } + + /* Setup the data */ + if (pdu->isp_datalen) { + idm_task_t *idt; + idm_buf_t *idb; + iscsi_data_hdr_t *ihp; + ihp = (iscsi_data_hdr_t *)pdu->isp_hdr; + /* Write of immediate data */ + if (ic->ic_ffp && + (ihp->opcode == ISCSI_OP_SCSI_CMD || + ihp->opcode == ISCSI_OP_SCSI_DATA)) { + idt = idm_task_find(ic, ihp->itt, ihp->ttt); + if (idt) { + mutex_enter(&idt->idt_mutex); + idb = idm_buf_find(&idt->idt_outbufv, 0); + mutex_exit(&idt->idt_mutex); + idb->idb_xfer_len += pdu->isp_datalen; + } + } + + iov[iovlen].iov_base = (caddr_t)pdu->isp_data; + iov[iovlen].iov_len = pdu->isp_datalen; + total_len += iov[iovlen].iov_len; + iovlen++; + } + + /* Setup the data pad if necessary */ + pad_len = ((ISCSI_PAD_WORD_LEN - + (pdu->isp_datalen & (ISCSI_PAD_WORD_LEN - 1))) & + (ISCSI_PAD_WORD_LEN - 1)); + + if (pad_len) { + bzero(pad, sizeof (pad)); + iov[iovlen].iov_base = (void *)&pad; + iov[iovlen].iov_len = pad_len; + total_len += iov[iovlen].iov_len; + iovlen++; + } + + /* + * Setup the data digest if enabled. Data-digest is not sent + * for login-phase PDUs. + */ + if ((ic->ic_conn_flags & IDM_CONN_DATA_DIGEST) && + ((pdu->isp_flags & IDM_PDU_LOGIN_TX) == 0) && + (pdu->isp_datalen || pad_len)) { + /* + * RFC3720/10.2.3: A zero-length Data Segment also + * implies a zero-length data digest. + */ + if (pdu->isp_datalen) { + data_digest_crc = idm_crc32c(pdu->isp_data, + pdu->isp_datalen); + } + if (pad_len) { + data_digest_crc = idm_crc32c_continued(&pad, + pad_len, data_digest_crc); + } + + iov[iovlen].iov_base = (caddr_t)&data_digest_crc; + iov[iovlen].iov_len = sizeof (data_digest_crc); + total_len += iov[iovlen].iov_len; + iovlen++; + } + + /* Transmit the PDU */ + if (idm_iov_sosend(so_conn->ic_so, &iov[0], iovlen, + total_len) != 0) { + /* Set error status */ + IDM_CONN_LOG(CE_WARN, + "idm_so_tx: failed to transmit the PDU, so: %p ic: %p " + "data: %p", (void *) so_conn->ic_so, (void *) ic, + (void *) pdu->isp_data); + status = IDM_STATUS_IO; + } + + /* + * Success does not mean that the PDU actually reached the + * remote node since it could get dropped along the way. + */ + idm_pdu_complete(pdu, status); + + return (status); +} + +/* + * The idm_so_buf_tx_to_ini() is used by the target iSCSI layer to transmit the + * Data-In PDUs using sockets. Based on the negotiated MaxRecvDataSegmentLength, + * the buffer is segmented into a sequence of Data-In PDUs, ordered by DataSN. + * A target can invoke this function multiple times for a single read command + * (identified by the same ITT) to split the input into several sequences. + * + * DataSN starts with 0 for the first data PDU of an input command and advances + * by 1 for each subsequent data PDU. Each sequence will have its own F bit, + * which is set to 1 for the last data PDU of a sequence. + * + * Scope for Prototype build: + * The data PDUs within a sequence will be sent in order with the buffer offset + * in increasing order. i.e. initiator and target must have negotiated the + * "DataPDUInOrder" to "Yes". The order between sequences is not enforced. + * + * Caller holds idt->idt_mutex + */ +static idm_status_t +idm_so_buf_tx_to_ini(idm_task_t *idt, idm_buf_t *idb) +{ + idm_so_conn_t *so_conn = idb->idb_ic->ic_transport_private; + idm_pdu_t tmppdu; + + ASSERT(mutex_owned(&idt->idt_mutex)); + + /* + * Put the idm_buf_t on the tx queue. It will be transmitted by + * idm_sotx_thread. + */ + mutex_enter(&so_conn->ic_tx_mutex); + + if (!so_conn->ic_tx_thread_running) { + mutex_exit(&so_conn->ic_tx_mutex); + /* + * Don't release idt->idt_mutex since we're supposed to hold + * in when calling idm_buf_tx_to_ini_done + */ + idm_buf_tx_to_ini_done(idt, idb, IDM_STATUS_ABORTED); + return (IDM_STATUS_FAIL); + } + + /* + * Build a template for the data PDU headers we will use so that + * the SN values will stay consistent with other PDU's we are + * transmitting like R2T and SCSI status. + */ + bzero(&idb->idb_data_hdr_tmpl, sizeof (iscsi_hdr_t)); + tmppdu.isp_hdr = &idb->idb_data_hdr_tmpl; + (*idt->idt_ic->ic_conn_ops.icb_build_hdr)(idt, &tmppdu, + ISCSI_OP_SCSI_DATA_RSP); + idb->idb_tx_thread = B_TRUE; + list_insert_tail(&so_conn->ic_tx_list, (void *)idb); + cv_signal(&so_conn->ic_tx_cv); + mutex_exit(&so_conn->ic_tx_mutex); + mutex_exit(&idt->idt_mutex); + + /* + * Returning success here indicates the transfer was successfully + * dispatched -- it does not mean that the transfer completed + * successfully. + */ + return (IDM_STATUS_SUCCESS); +} + +/* + * The idm_so_buf_rx_from_ini() is used by the target iSCSI layer to specify the + * data blocks it is ready to receive from the initiator in response to a WRITE + * SCSI command. The target iSCSI layer passes the information about the desired + * data blocks to the initiator in one R2T PDU. The receiving buffer, the buffer + * offset and datalen are passed via the 'idb' argument. + * + * Scope for Prototype build: + * R2Ts are required for any Data-Out PDU, i.e. initiator and target must have + * negotiated the "InitialR2T" to "Yes". + * + * Caller holds idt->idt_mutex + */ +static idm_status_t +idm_so_buf_rx_from_ini(idm_task_t *idt, idm_buf_t *idb) +{ + idm_pdu_t *pdu; + iscsi_rtt_hdr_t *rtt; + + ASSERT(mutex_owned(&idt->idt_mutex)); + + pdu = kmem_cache_alloc(idm.idm_sotx_pdu_cache, KM_SLEEP); + pdu->isp_ic = idt->idt_ic; + bzero(pdu->isp_hdr, sizeof (iscsi_rtt_hdr_t)); + + /* iSCSI layer fills the TTT, ITT, StatSN, ExpCmdSN, MaxCmdSN */ + (*idt->idt_ic->ic_conn_ops.icb_build_hdr)(idt, pdu, ISCSI_OP_RTT_RSP); + + /* set the rttsn, rtt.flags, rtt.data_offset and rtt.data_length */ + rtt = (iscsi_rtt_hdr_t *)(pdu->isp_hdr); + + rtt->opcode = ISCSI_OP_RTT_RSP; + rtt->flags = ISCSI_FLAG_FINAL; + rtt->data_offset = htonl(idb->idb_bufoffset); + rtt->data_length = htonl(idb->idb_xfer_len); + rtt->rttsn = htonl(idt->idt_exp_rttsn++); + + /* Keep track of buffer offsets */ + idb->idb_exp_offset = idb->idb_bufoffset; + mutex_exit(&idt->idt_mutex); + + /* + * Transmit the PDU. Call the internal routine directly as there + * is already implicit ordering of the PDU. + */ + (void) idm_i_so_tx(pdu); + + return (IDM_STATUS_SUCCESS); +} + +static idm_status_t +idm_so_buf_alloc(idm_buf_t *idb, uint64_t buflen) +{ + idb->idb_buf = kmem_alloc(buflen, KM_NOSLEEP); + if (idb->idb_buf == NULL) { + IDM_CONN_LOG(CE_NOTE, + "idm_so_buf_alloc: failed buffer allocation"); + return (IDM_STATUS_FAIL); + } + return (IDM_STATUS_SUCCESS); +} + +/* ARGSUSED */ +static idm_status_t +idm_so_buf_setup(idm_buf_t *idb) +{ + /* nothing to do here */ + return (IDM_STATUS_SUCCESS); +} + +/* ARGSUSED */ +static void +idm_so_buf_teardown(idm_buf_t *idb) +{ + /* nothing to do here */ +} + +static void +idm_so_buf_free(idm_buf_t *idb) +{ + kmem_free(idb->idb_buf, idb->idb_buflen); +} + +idm_status_t +idm_so_send_buf_region(idm_task_t *idt, uint8_t opcode, idm_buf_t *idb, + uint32_t buf_region_offset, uint32_t buf_region_length) +{ + idm_conn_t *ic; + uint32_t max_dataseglen; + size_t remainder, chunk; + uint32_t data_offset = buf_region_offset; + iscsi_data_hdr_t *bhs; + idm_pdu_t *pdu; + + ASSERT(mutex_owned(&idt->idt_mutex)); + + ic = idt->idt_ic; + + max_dataseglen = 8192; /* Need value from login negotiation */ + remainder = buf_region_length; + + while (remainder) { + if (idt->idt_state != TASK_ACTIVE) { + ASSERT((idt->idt_state != TASK_IDLE) && + (idt->idt_state != TASK_COMPLETE)); + return (IDM_STATUS_ABORTED); + } + + /* check to see if we need to chunk the data */ + if (remainder > max_dataseglen) { + chunk = max_dataseglen; + } else { + chunk = remainder; + } + + /* Data PDU headers will always be sizeof (iscsi_hdr_t) */ + pdu = kmem_cache_alloc(idm.idm_sotx_pdu_cache, KM_SLEEP); + pdu->isp_ic = ic; + + /* + * For target we've already built a build a header template + * to use during the transfer. Use this template so that + * the SN values stay consistent with any unrelated PDU's + * being transmitted. + */ + if (opcode == ISCSI_OP_SCSI_DATA_RSP) { + bcopy(&idb->idb_data_hdr_tmpl, pdu->isp_hdr, + sizeof (iscsi_hdr_t)); + } else { + /* + * OK for now, but we should remove this bzero and + * make sure the build_hdr function is initializing the + * header properly + */ + bzero(pdu->isp_hdr, sizeof (iscsi_hdr_t)); + + /* + * setup iscsi data hdr + * callback to the iSCSI layer to fill in the BHS + * CmdSN, StatSN, ExpCmdSN, MaxCmdSN, TTT, ITT and + * opcode + */ + (*ic->ic_conn_ops.icb_build_hdr)(idt, pdu, opcode); + } + + /* + * Set DataSN, data offset, and flags in BHS + * For the prototype build, A = 0, S = 0, U = 0 + */ + bhs = (iscsi_data_hdr_t *)(pdu->isp_hdr); + + bhs->datasn = htonl(idt->idt_exp_datasn++); + + hton24(bhs->dlength, chunk); + bhs->offset = htonl(idb->idb_bufoffset + data_offset); + + if (chunk == remainder) { + bhs->flags = ISCSI_FLAG_FINAL; /* F bit set to 1 */ + } + + /* setup data */ + pdu->isp_data = (uint8_t *)idb->idb_buf + data_offset; + pdu->isp_datalen = (uint_t)chunk; + remainder -= chunk; + data_offset += chunk; + + /* + * Now that we're done working with idt_exp_datasn, + * idt->idt_state and idb->idb_bufoffset we can release + * the task lock -- don't want to hold it across the + * call to idm_i_so_tx since we could block. + */ + mutex_exit(&idt->idt_mutex); + + /* + * Transmit the PDU. Call the internal routine directly + * as there is already implicit ordering. + */ + (void) idm_i_so_tx(pdu); + + mutex_enter(&idt->idt_mutex); + } + + return (IDM_STATUS_SUCCESS); +} + +/* + * TX PDU cache + */ +/* ARGSUSED */ +int +idm_sotx_pdu_constructor(void *hdl, void *arg, int flags) +{ + idm_pdu_t *pdu = hdl; + + bzero(pdu, sizeof (idm_pdu_t)); + pdu->isp_hdr = (iscsi_hdr_t *)(pdu + 1); /* Ptr arithmetic */ + pdu->isp_hdrlen = sizeof (iscsi_hdr_t); + pdu->isp_callback = idm_sotx_cache_pdu_cb; + pdu->isp_magic = IDM_PDU_MAGIC; + bzero(pdu->isp_hdr, sizeof (iscsi_hdr_t)); + + return (0); +} + +/* ARGSUSED */ +void +idm_sotx_cache_pdu_cb(idm_pdu_t *pdu, idm_status_t status) +{ + /* reset values between use */ + pdu->isp_datalen = 0; + + kmem_cache_free(idm.idm_sotx_pdu_cache, pdu); +} + +/* + * RX PDU cache + */ +/* ARGSUSED */ +int +idm_sorx_pdu_constructor(void *hdl, void *arg, int flags) +{ + idm_pdu_t *pdu = hdl; + + bzero(pdu, sizeof (idm_pdu_t)); + pdu->isp_magic = IDM_PDU_MAGIC; + pdu->isp_hdr = (iscsi_hdr_t *)(pdu + 1); /* Ptr arithmetic */ + pdu->isp_callback = idm_sorx_cache_pdu_cb; + + return (0); +} + +/* ARGSUSED */ +static void +idm_sorx_cache_pdu_cb(idm_pdu_t *pdu, idm_status_t status) +{ + pdu->isp_iovlen = 0; + pdu->isp_sorx_buf = 0; + kmem_cache_free(idm.idm_sorx_pdu_cache, pdu); +} + +static void +idm_sorx_addl_pdu_cb(idm_pdu_t *pdu, idm_status_t status) +{ + /* + * We had to modify our cached RX PDU with a longer header buffer + * and/or a longer data buffer. Release the new buffers and fix + * the fields back to what we would expect for a cached RX PDU. + */ + if (pdu->isp_flags & IDM_PDU_ADDL_HDR) { + kmem_free(pdu->isp_hdr, pdu->isp_hdrlen); + } + if (pdu->isp_flags & IDM_PDU_ADDL_DATA) { + kmem_free(pdu->isp_data, pdu->isp_datalen); + } + pdu->isp_hdr = (iscsi_hdr_t *)(pdu + 1); + pdu->isp_hdrlen = sizeof (iscsi_hdr_t); + pdu->isp_data = NULL; + pdu->isp_datalen = 0; + pdu->isp_sorx_buf = 0; + pdu->isp_callback = idm_sorx_cache_pdu_cb; + idm_sorx_cache_pdu_cb(pdu, status); +} + +/* + * This thread is only active when I/O is queued for transmit + * because the socket is busy. + */ +void +idm_sotx_thread(void *arg) +{ + idm_conn_t *ic = arg; + idm_tx_obj_t *object, *next; + idm_so_conn_t *so_conn; + idm_status_t status = IDM_STATUS_SUCCESS; + + idm_conn_hold(ic); + + mutex_enter(&ic->ic_mutex); + so_conn = ic->ic_transport_private; + so_conn->ic_tx_thread_running = B_TRUE; + so_conn->ic_tx_thread_did = so_conn->ic_tx_thread->t_did; + cv_signal(&ic->ic_cv); + mutex_exit(&ic->ic_mutex); + + mutex_enter(&so_conn->ic_tx_mutex); + + while (so_conn->ic_tx_thread_running) { + while (list_is_empty(&so_conn->ic_tx_list)) { + DTRACE_PROBE1(soconn__tx__sleep, idm_conn_t *, ic); + cv_wait(&so_conn->ic_tx_cv, &so_conn->ic_tx_mutex); + DTRACE_PROBE1(soconn__tx__wakeup, idm_conn_t *, ic); + + if (!so_conn->ic_tx_thread_running) { + goto tx_bail; + } + } + + object = (idm_tx_obj_t *)list_head(&so_conn->ic_tx_list); + list_remove(&so_conn->ic_tx_list, object); + mutex_exit(&so_conn->ic_tx_mutex); + + switch (object->idm_tx_obj_magic) { + case IDM_PDU_MAGIC: + DTRACE_PROBE2(soconn__tx__pdu, idm_conn_t *, ic, + idm_pdu_t *, (idm_pdu_t *)object); + + status = idm_i_so_tx((idm_pdu_t *)object); + break; + + case IDM_BUF_MAGIC: { + idm_buf_t *idb = (idm_buf_t *)object; + idm_task_t *idt = idb->idb_task_binding; + + DTRACE_PROBE2(soconn__tx__buf, idm_conn_t *, ic, + idm_buf_t *, idb); + + mutex_enter(&idt->idt_mutex); + status = idm_so_send_buf_region(idt, + ISCSI_OP_SCSI_DATA_RSP, idb, 0, idb->idb_xfer_len); + + /* + * TX thread owns the buffer so we expect it to + * be "in transport" + */ + ASSERT(idb->idb_in_transport); + /* + * idm_buf_tx_to_ini_done releases idt->idt_mutex + */ + idm_buf_tx_to_ini_done(idt, idb, status); + break; + } + + default: + IDM_CONN_LOG(CE_WARN, "idm_sotx_thread: Unknown magic " + "(0x%08x)", object->idm_tx_obj_magic); + status = IDM_STATUS_FAIL; + } + + mutex_enter(&so_conn->ic_tx_mutex); + + if (status != IDM_STATUS_SUCCESS) { + so_conn->ic_tx_thread_running = B_FALSE; + idm_conn_event(ic, CE_TRANSPORT_FAIL, status); + } + } + + /* + * Before we leave, we need to abort every item remaining in the + * TX list. + */ + +tx_bail: + object = (idm_tx_obj_t *)list_head(&so_conn->ic_tx_list); + + while (object != NULL) { + next = list_next(&so_conn->ic_tx_list, object); + + list_remove(&so_conn->ic_tx_list, object); + switch (object->idm_tx_obj_magic) { + case IDM_PDU_MAGIC: + idm_pdu_complete((idm_pdu_t *)object, + IDM_STATUS_ABORTED); + break; + + case IDM_BUF_MAGIC: { + idm_buf_t *idb = (idm_buf_t *)object; + idm_task_t *idt = idb->idb_task_binding; + mutex_exit(&so_conn->ic_tx_mutex); + mutex_enter(&idt->idt_mutex); + /* + * TX thread owns the buffer so we expect it to + * be "in transport" + */ + ASSERT(idb->idb_in_transport); + /* + * idm_buf_tx_to_ini_done releases idt->idt_mutex + */ + idm_buf_tx_to_ini_done(idt, idb, IDM_STATUS_ABORTED); + mutex_enter(&so_conn->ic_tx_mutex); + break; + } + default: + IDM_CONN_LOG(CE_WARN, + "idm_sotx_thread: Unexpected magic " + "(0x%08x)", object->idm_tx_obj_magic); + } + + object = next; + } + + mutex_exit(&so_conn->ic_tx_mutex); + idm_conn_rele(ic); + thread_exit(); + /*NOTREACHED*/ +} diff --git a/usr/src/uts/common/io/idm/idm_text.c b/usr/src/uts/common/io/idm/idm_text.c new file mode 100644 index 000000000000..ee34dd115dfa --- /dev/null +++ b/usr/src/uts/common/io/idm/idm_text.c @@ -0,0 +1,1709 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +extern int +iscsi_base64_str_to_binary(char *hstr, int hstr_len, + uint8_t *binary, int binary_buf_len, int *out_len); + + +static const char idm_hex_to_ascii[] = "0123456789abcdefABCDEF"; + +static const idm_kv_xlate_t idm_kvpair_xlate[] = { + /* + * iSCSI Security Text Keys and Authentication Methods + */ + + { KI_AUTH_METHOD, "AuthMethod", KT_LIST_OF_VALUES, B_FALSE }, + /* + * For values with RFC comments we need to read the RFC to see + * what type is appropriate. For now just treat the value as + * text. + */ + + /* Kerberos */ + { KI_KRB_AP_REQ, "KRB_AP_REQ", KT_TEXT /* RFC1510 */, B_TRUE}, + { KI_KRB_AP_REP, "KRB_AP_REP", KT_TEXT /* RFC1510 */, B_TRUE}, + + /* SPKM */ + { KI_SPKM_REQ, "SPKM_REQ", KT_TEXT /* RFC2025 */, B_TRUE}, + { KI_SPKM_ERROR, "SPKM_ERROR", KT_TEXT /* RFC2025 */, B_TRUE}, + { KI_SPKM_REP_TI, "SPKM_REP_TI", KT_TEXT /* RFC2025 */, B_TRUE}, + { KI_SPKM_REP_IT, "SPKM_REP_IT", KT_TEXT /* RFC2025 */, B_TRUE}, + + /* + * SRP + * U, s, A, B, M, and H(A | M | K) are defined in [RFC2945] + */ + { KI_SRP_U, "SRP_U", KT_TEXT /* */, B_TRUE}, + { KI_TARGET_AUTH, "TargetAuth", KT_BOOLEAN, B_TRUE}, + { KI_SRP_GROUP, "SRP_GROUP", KT_LIST_OF_VALUES /* */, B_FALSE}, + { KI_SRP_A, "SRP_A", KT_TEXT /* */, B_TRUE}, + { KI_SRP_B, "SRP_B", KT_TEXT /* */, B_TRUE}, + { KI_SRP_M, "SRP_M", KT_TEXT /* */, B_TRUE}, + { KI_SRM_HM, "SRP_HM", KT_TEXT /* */, B_TRUE}, + + /* + * CHAP + */ + { KI_CHAP_A, "CHAP_A", KT_LIST_OF_VALUES /* */, B_FALSE }, + { KI_CHAP_I, "CHAP_I", KT_NUMERICAL /* */, B_TRUE }, + { KI_CHAP_C, "CHAP_C", KT_BINARY /* */, B_TRUE }, + { KI_CHAP_N, "CHAP_N", KT_TEXT /* */, B_TRUE }, + { KI_CHAP_R, "CHAP_R", KT_BINARY /* */, B_TRUE }, + + + /* + * ISCSI Operational Parameter Keys + */ + { KI_HEADER_DIGEST, "HeaderDigest", KT_LIST_OF_VALUES, B_FALSE }, + { KI_DATA_DIGEST, "DataDigest", KT_LIST_OF_VALUES, B_FALSE }, + { KI_MAX_CONNECTIONS, "MaxConnections", KT_NUMERICAL, B_FALSE }, + { KI_SEND_TARGETS, "SendTargets", KT_TEXT, B_FALSE }, + { KI_TARGET_NAME, "TargetName", KT_ISCSI_NAME, B_TRUE}, + { KI_INITIATOR_NAME, "InitiatorName", KT_ISCSI_NAME, B_TRUE}, + { KI_TARGET_ALIAS, "TargetAlias", KT_ISCSI_LOCAL_NAME, B_TRUE}, + { KI_INITIATOR_ALIAS, "InitiatorAlias", KT_ISCSI_LOCAL_NAME, B_TRUE}, + { KI_TARGET_ADDRESS, "TargetAddress", KT_TEXT, B_TRUE}, + { KI_TARGET_PORTAL_GROUP_TAG, "TargetPortalGroupTag", + KT_NUMERICAL, B_TRUE }, + { KI_INITIAL_R2T, "InitialR2T", KT_BOOLEAN, B_FALSE }, + { KI_IMMEDIATE_DATA, "ImmediateData", KT_BOOLEAN, B_FALSE }, + { KI_MAX_RECV_DATA_SEGMENT_LENGTH, "MaxRecvDataSegmentLength", + KT_NUMERICAL /* 512 to 2^24 - 1 */, B_TRUE }, + { KI_MAX_BURST_LENGTH, "MaxBurstLength", + KT_NUMERICAL /* 512 to 2^24 - 1 */, B_FALSE }, + { KI_FIRST_BURST_LENGTH, "FirstBurstLength", + KT_NUMERICAL /* 512 to 2^24 - 1 */, B_FALSE }, + { KI_DEFAULT_TIME_2_WAIT, "DefaultTime2Wait", + KT_NUMERICAL /* 0 to 2600 */, B_FALSE }, + { KI_DEFAULT_TIME_2_RETAIN, "DefaultTime2Retain", + KT_NUMERICAL /* 0 to 2600 */, B_FALSE }, + { KI_MAX_OUTSTANDING_R2T, "MaxOutstandingR2T", + KT_NUMERICAL /* 1 to 65535 */, B_FALSE }, + { KI_DATA_PDU_IN_ORDER, "DataPDUInOrder", KT_BOOLEAN, B_FALSE }, + { KI_DATA_SEQUENCE_IN_ORDER, "DataSequenceInOrder", + KT_BOOLEAN, B_FALSE }, + { KI_ERROR_RECOVERY_LEVEL, "ErrorRecoveryLevel", + KT_NUMERICAL /* 0 to 2 */, B_FALSE }, + { KI_SESSION_TYPE, "SessionType", KT_TEXT, B_TRUE }, + { KI_OFMARKER, "OFMarker", KT_BOOLEAN, B_FALSE }, + { KI_OFMARKERINT, "OFMarkerInt", KT_NUMERIC_RANGE, B_FALSE }, + { KI_IFMARKER, "IFMarker", KT_BOOLEAN, B_FALSE }, + { KI_IFMARKERINT, "IFMarkerInt", KT_NUMERIC_RANGE, B_FALSE }, + + /* + * iSER-specific keys + */ + { KI_RDMA_EXTENSIONS, "RDMAExtensions", KT_BOOLEAN, B_FALSE }, + { KI_TARGET_RECV_DATA_SEGMENT_LENGTH, "TargetRecvDataSegmentLength", + KT_NUMERICAL /* 512 to 2^24 - 1 */, B_FALSE }, + { KI_INITIATOR_RECV_DATA_SEGMENT_LENGTH, + "InitiatorRecvDataSegmentLength", + KT_NUMERICAL /* 512 to 2^24 - 1 */, B_FALSE }, + { KI_MAX_OUTSTANDING_UNEXPECTED_PDUS, "MaxOutstandingUnexpectedPDUs", + KT_NUMERICAL /* 2 to 2^32 - 1 | 0 */, B_TRUE }, + + /* + * Table terminator. The type KT_TEXT will allow the response + * value of "NotUnderstood". + */ + { KI_MAX_KEY, NULL, KT_TEXT, B_TRUE } /* Terminator */ +}; + + +#define TEXTBUF_CHUNKSIZE 8192 + +typedef struct { + char *itb_mem; + int itb_offset; + int itb_mem_len; +} idm_textbuf_t; + +/* + * Ignore all but the following keys during security negotiation + * + * SessionType + * InitiatorName + * TargetName + * TargetAddress + * InitiatorAlias + * TargetAlias + * TargetPortalGroupTag + * AuthMethod and associated auth keys + */ + +static int idm_keyvalue_get_next(char **tb_scan, int *tb_len, + char **key, int *keylen, char **value); + +static int idm_nvlist_add_kv(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, + char *value); + +static int idm_nvlist_add_string(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value); + +static int idm_nvlist_add_boolean(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value); + +static int idm_nvlist_add_binary(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value); + +static int idm_nvlist_add_large_numerical(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value); + +static int idm_nvlist_add_numerical(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value); + +static int idm_nvlist_add_numeric_range(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value); + +static int idm_nvlist_add_list_of_values(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value); + +static int idm_itextbuf_add_nvpair(nvpair_t *nvp, idm_textbuf_t *itb); + +static int idm_itextbuf_add_string(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); + +static int idm_itextbuf_add_boolean(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); + +static int idm_itextbuf_add_binary(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); + +static int idm_itextbuf_add_large_numerical(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); + +static int idm_itextbuf_add_numerical(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); + +static int idm_itextbuf_add_numeric_range(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); + +static int idm_itextbuf_add_list_of_values(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb); + +static void textbuf_memcpy(idm_textbuf_t *itb, void *mem, int mem_len); + +static void textbuf_strcpy(idm_textbuf_t *itb, char *str); + +static void textbuf_append_char(idm_textbuf_t *itb, char c); + +static void textbuf_terminate_kvpair(idm_textbuf_t *itb); + +static int idm_ascii_to_hex(char *enc_hex_byte, uint8_t *bin_val); + +static int idm_base16_str_to_binary(char *hstr, int hstr_len, + uint8_t *binary, int binary_length); + +static size_t idm_strcspn(const char *string, const char *charset); + +static size_t idm_strnlen(const char *str, size_t maxlen); + +/* + * Processes all whole iSCSI name-value pairs in a text buffer and adds + * a corresponding Solaris nvpair_t to the provided nvlist. If the last + * iSCSI name-value pair in textbuf is truncated (which can occur when + * the request spans multiple PDU's) then upon return textbuf will + * point to the truncated iSCSI name-value pair in the buffer and + * textbuflen will contain the remaining bytes in the buffer. The + * caller can save off this fragment of the iSCSI name-value pair for + * use when the next PDU in the request arrives. + * + * textbuflen includes the trailing 0x00! + */ + +int +idm_textbuf_to_nvlist(nvlist_t *nvl, char **textbuf, int *textbuflen) +{ + int rc = 0; + char *tbscan, *key, *value; + int tblen, keylen; + + tbscan = *textbuf; + tblen = *textbuflen; + + for (;;) { + if ((rc = idm_keyvalue_get_next(&tbscan, &tblen, + &key, &keylen, &value)) != 0) { + /* There was a problem reading the key/value pair */ + break; + } + + if ((rc = idm_nvlist_add_keyvalue(nvl, + key, keylen, value)) != 0) { + /* Something was wrong with either the key or value */ + break; + } + + if (tblen == 0) { + /* End of text buffer */ + break; + } + } + + *textbuf = tbscan; + *textbuflen = tblen; + + return (rc); +} + +/* + * If a test buffer starts with an ISCSI name-value pair fragment (a + * continuation from a previous buffer) return the length of the fragment + * contained in this buffer. We do not handle name-value pairs that span + * more than two buffers so if this buffer does not contain the remainder + * of the name value pair the function will return 0. If the first + * name-value pair in the buffer is complete the functionw will return 0. + */ +int +idm_textbuf_to_firstfraglen(void *textbuf, int textbuflen) +{ + return (idm_strnlen(textbuf, textbuflen)); +} + +static int +idm_keyvalue_get_next(char **tb_scan, int *tb_len, + char **key, int *keylen, char **value) +{ + /* + * Caller doesn't need "valuelen" returned since "value" will + * always be a NULL-terminated string. + */ + size_t total_len, valuelen; + + /* + * How many bytes to the first '\0'? This represents the total + * length of our iSCSI key/value pair. + */ + total_len = idm_strnlen(*tb_scan, *tb_len); + if (total_len == *tb_len) { + /* + * No '\0', perhaps this key/value pair is continued in + * another buffer + */ + return (E2BIG); + } + + /* + * Found NULL, so this is a possible key-value pair. At + * the same time we've validated that there is actually a + * NULL in this string so it's safe to use regular + * string functions (i.e. strcpy instead of strncpy) + */ + *key = *tb_scan; + *keylen = idm_strcspn(*tb_scan, "="); + + if (*keylen == total_len) { + /* No '=', bad format */ + return (EINVAL); + } + + *tb_scan += *keylen + 1; /* Skip the '=' */ + *tb_len -= *keylen + 1; + + /* + * The remaining text after the '=' is the value + */ + *value = *tb_scan; + valuelen = total_len - (*keylen + 1); + + *tb_scan += valuelen + 1; /* Skip the '\0' */ + *tb_len -= valuelen + 1; + + return (0); +} + +const idm_kv_xlate_t * +idm_lookup_kv_xlate(const char *key, int keylen) +{ + const idm_kv_xlate_t *ikvx = &idm_kvpair_xlate[0]; + + /* + * Look for a matching key value in the key/value pair table. + * The matching entry in the table will tell us how to encode + * the key and value in the nvlist. If we don't recognize + * the key then we will simply encode it in string format. + * The login or text request code can generate the appropriate + * "not understood" resposne. + */ + while (ikvx->ik_key_id != KI_MAX_KEY) { + /* + * Compare strings. "key" is not NULL-terminated so + * use strncmp. Since we are using strncmp we + * need to check that the lengths match, otherwise + * we might unintentionally lookup "TargetAddress" + * with a key of "Target" (or something similar). + * + * "value" is NULL-terminated so we can use it as + * a regular string. + */ + if ((strncmp(ikvx->ik_key_name, key, keylen) == 0) && + (strlen(ikvx->ik_key_name) == keylen)) { + /* Exit the loop since we found a match */ + break; + } + + /* No match, look at the next entry */ + ikvx++; + } + + return (ikvx); +} + +static int +idm_nvlist_add_kv(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value) +{ + int rc; + + switch (ikvx->ik_idm_type) { + case KT_TEXT: + case KT_SIMPLE: + case KT_ISCSI_NAME: + case KT_ISCSI_LOCAL_NAME: + rc = idm_nvlist_add_string(nvl, ikvx, value); + break; + case KT_BOOLEAN: + rc = idm_nvlist_add_boolean(nvl, ikvx, value); + break; + case KT_REGULAR_BINARY: + case KT_LARGE_BINARY: + case KT_BINARY: + rc = idm_nvlist_add_binary(nvl, ikvx, value); + break; + case KT_LARGE_NUMERICAL: + rc = idm_nvlist_add_large_numerical(nvl, ikvx, + value); + break; + case KT_NUMERICAL: + rc = idm_nvlist_add_numerical(nvl, ikvx, + value); + break; + case KT_NUMERIC_RANGE: + rc = idm_nvlist_add_numeric_range(nvl, ikvx, + value); + break; + case KT_LIST_OF_VALUES: + rc = idm_nvlist_add_list_of_values(nvl, ikvx, + value); + break; + default: + ASSERT(0); /* This should never happen */ + break; + } + if (rc != 0) { + /* could be one of the text constants */ + rc = idm_nvlist_add_string(nvl, ikvx, value); + } + + return (rc); +} + +static int +idm_nvlist_add_string(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value) +{ + return (nvlist_add_string(nvl, ikvx->ik_key_name, value)); +} + +static int +idm_nvlist_add_boolean(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value) +{ + int rc; + boolean_t bool_val; + + if (strcasecmp(value, "Yes") == 0) { + bool_val = B_TRUE; + } else if (strcasecmp(value, "No") == 0) { + bool_val = B_FALSE; + } else { + return (EINVAL); + } + + rc = nvlist_add_boolean_value(nvl, ikvx->ik_key_name, bool_val); + + return (rc); +} + +static boolean_t +kv_is_hex(char *value) +{ + return ((strncmp(value, "0x", strlen("0x")) == 0) || + (strncmp(value, "0X", strlen("0X")) == 0)); +} + +static boolean_t +kv_is_base64(char *value) +{ + return ((strncmp(value, "0b", strlen("0b")) == 0) || + (strncmp(value, "0B", strlen("0B")) == 0)); +} + + +static int +idm_nvlist_add_binary(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value) +{ + int rc; + int value_length; + uint64_t uint64_value; + int binary_length; + uchar_t *binary_array; + + /* + * A binary value can be either decimal, hex or base64. If it's + * decimal then the encoded string must be less than 64 bits in + * length (8 characters). In all cases we will convert the + * included value to a byte array starting with the MSB. The + * assumption is that values meant to be treated as integers will + * use the "numerical" and "large numerical" types. + */ + if (kv_is_hex(value)) { + value += strlen("0x"); + value_length = strlen(value); + binary_length = (value_length + 1) / 2; + binary_array = kmem_alloc(binary_length, KM_SLEEP); + + if (idm_base16_str_to_binary(value, value_length, + binary_array, binary_length) != 0) { + kmem_free(binary_array, binary_length); + return (EINVAL); + } + + rc = nvlist_add_byte_array(nvl, ikvx->ik_key_name, + binary_array, binary_length); + + kmem_free(binary_array, binary_length); + + return (rc); + + } else if (kv_is_base64(value)) { + value += strlen("0b"); + value_length = strlen(value); + binary_array = kmem_alloc(value_length, KM_NOSLEEP); + if (binary_array == NULL) { + return (ENOMEM); + } + + if (iscsi_base64_str_to_binary(value, value_length, + binary_array, value_length, &binary_length) != 0) { + kmem_free(binary_array, value_length); + return (EINVAL); + } + + rc = nvlist_add_byte_array(nvl, ikvx->ik_key_name, + binary_array, binary_length); + + kmem_free(binary_array, value_length); + + return (rc); + } else { + /* + * Decimal value (not permitted for "large-binary_value" so + * it must be smaller than 64 bits. It's not really + * clear from the RFC what a decimal-binary-value might + * represent but presumably it should be treated the same + * as a hex or base64 value. Therefore we'll convert it + * to an array of bytes. + */ + if ((rc = idm_strtoull(value, NULL, 0, + (u_longlong_t *)&uint64_value)) != 0) + return (rc); + + rc = nvlist_add_byte_array(nvl, ikvx->ik_key_name, + (uint8_t *)&uint64_value, sizeof (uint64_value)); + + return (rc); + } + + /* NOTREACHED */ +} + + +static int +idm_nvlist_add_large_numerical(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value) +{ + /* + * A "large numerical" value can be larger than 64-bits. Since + * there is no upper bound on the size of the value, we will + * punt and store it in string form. We could also potentially + * treat the value as binary data. + */ + return (nvlist_add_string(nvl, ikvx->ik_key_name, value)); +} + + +static int +idm_nvlist_add_numerical(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value) +{ + int rc; + uint64_t uint64_value; + + /* + * "Numerical" values in the iSCSI standard are up to 64-bits wide. + * On a 32-bit system we could see an overflow here during conversion. + * This shouldn't happen with real-world values for the current + * iSCSI parameters of "numerical" type. + */ + rc = idm_strtoull(value, NULL, 0, (u_longlong_t *)&uint64_value); + if (rc == 0) { + rc = nvlist_add_uint64(nvl, ikvx->ik_key_name, uint64_value); + } + + return (rc); +} + + +static int +idm_nvlist_add_numeric_range(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *range) +{ + nvlist_t *range_nvl; + char *val_scan = range; + uint64_t start_val, end_val; + int val_len, range_len; + int rc; + + /* We'll store the range an an nvlist with two values */ + rc = nvlist_alloc(&range_nvl, NV_UNIQUE_NAME, KM_NOSLEEP); + if (rc != 0) { + return (rc); + } + + /* + * We expect idm_keyvalue_get_next to ensure the string is + * terminated + */ + range_len = strlen(range); + + /* + * Find range separator + */ + val_len = idm_strcspn(val_scan, "~"); + + if (val_len == range_len) { + /* invalid range */ + nvlist_free(range_nvl); + return (EINVAL); + } + + /* + * Start value + */ + *(val_scan + val_len + 1) = '\0'; + rc = idm_strtoull(val_scan, NULL, 0, (u_longlong_t *)&start_val); + if (rc == 0) { + rc = nvlist_add_uint64(range_nvl, "start", start_val); + } + if (rc != 0) { + nvlist_free(range_nvl); + return (rc); + } + + /* + * End value + */ + val_scan += val_len + 1; + rc = idm_strtoull(val_scan, NULL, 0, (u_longlong_t *)&end_val); + if (rc == 0) { + rc = nvlist_add_uint64(range_nvl, "start", end_val); + } + if (rc != 0) { + nvlist_free(range_nvl); + return (rc); + } + + /* + * Now add the "range" nvlist to the main nvlist + */ + rc = nvlist_add_nvlist(nvl, ikvx->ik_key_name, range_nvl); + if (rc != 0) { + nvlist_free(range_nvl); + return (rc); + } + + nvlist_free(range_nvl); + return (0); +} + + +static int +idm_nvlist_add_list_of_values(nvlist_t *nvl, + const idm_kv_xlate_t *ikvx, char *value_list) +{ + char value_name[8]; + nvlist_t *value_list_nvl; + char *val_scan = value_list; + int value_index = 0; + int val_len, val_list_len; + int rc; + + rc = nvlist_alloc(&value_list_nvl, NV_UNIQUE_NAME, KM_NOSLEEP); + if (rc != 0) { + return (rc); + } + + /* + * We expect idm_keyvalue_get_next to ensure the string is + * terminated + */ + val_list_len = strlen(value_list); + if (val_list_len == 0) { + nvlist_free(value_list_nvl); + return (EINVAL); + } + + for (;;) { + (void) snprintf(value_name, 8, "value%d", value_index); + + val_len = idm_strcspn(val_scan, ","); + + if (*(val_scan + val_len) != '\0') { + *(val_scan + val_len) = '\0'; + } + rc = nvlist_add_string(value_list_nvl, value_name, val_scan); + if (rc != 0) { + nvlist_free(value_list_nvl); + return (rc); + } + + /* + * Move to next value, see if we're at the end of the value + * list + */ + val_scan += val_len + 1; + if (val_scan == value_list + val_list_len + 1) { + break; + } + + value_index++; + } + + rc = nvlist_add_nvlist(nvl, ikvx->ik_key_name, value_list_nvl); + if (rc != 0) { + nvlist_free(value_list_nvl); + return (rc); + } + + nvlist_free(value_list_nvl); + return (0); +} + +/* + * Convert an nvlist containing standard iSCSI key names and values into + * a text buffer with properly formatted iSCSI key-value pairs ready to + * transmit on the wire. *textbuf should be NULL and will be set to point + * the resulting text buffer. + */ + +int +idm_nvlist_to_textbuf(nvlist_t *nvl, char **textbuf, int *textbuflen, + int *validlen) +{ + int rc = 0; + nvpair_t *nvp = NULL; + idm_textbuf_t itb; + + bzero(&itb, sizeof (itb)); + + for (;;) { + nvp = nvlist_next_nvpair(nvl, nvp); + + if (nvp == NULL) { + /* Last nvpair in nvlist, we're done */ + break; + } + + if ((rc = idm_itextbuf_add_nvpair(nvp, &itb)) != 0) { + /* There was a problem building the key/value pair */ + break; + } + } + + *textbuf = itb.itb_mem; + *textbuflen = itb.itb_mem_len; + *validlen = itb.itb_offset; + + return (rc); +} + +static int +idm_itextbuf_add_nvpair(nvpair_t *nvp, + idm_textbuf_t *itb) +{ + int rc = 0; + char *key; + const idm_kv_xlate_t *ikvx; + + key = nvpair_name(nvp); + + ikvx = idm_lookup_kv_xlate(key, strlen(key)); + + /* + * Any key supplied by the initiator that is not in our table + * will be responded to with the string value "NotUnderstood". + * An example is a vendor specific key. + */ + ASSERT((ikvx->ik_key_id != KI_MAX_KEY) || + (nvpair_type(nvp) == DATA_TYPE_STRING)); + + /* + * Look for a matching key value in the key/value pair table. + * The matching entry in the table will tell us how to encode + * the key and value in the nvlist. + */ + switch (ikvx->ik_idm_type) { + case KT_TEXT: + case KT_SIMPLE: + case KT_ISCSI_NAME: + case KT_ISCSI_LOCAL_NAME: + rc = idm_itextbuf_add_string(nvp, ikvx, itb); + break; + case KT_BOOLEAN: + rc = idm_itextbuf_add_boolean(nvp, ikvx, itb); + break; + case KT_REGULAR_BINARY: + case KT_LARGE_BINARY: + case KT_BINARY: + rc = idm_itextbuf_add_binary(nvp, ikvx, itb); + break; + case KT_LARGE_NUMERICAL: + rc = idm_itextbuf_add_large_numerical(nvp, ikvx, itb); + break; + case KT_NUMERICAL: + rc = idm_itextbuf_add_numerical(nvp, ikvx, itb); + break; + case KT_NUMERIC_RANGE: + rc = idm_itextbuf_add_numeric_range(nvp, ikvx, itb); + break; + case KT_LIST_OF_VALUES: + rc = idm_itextbuf_add_list_of_values(nvp, ikvx, itb); + break; + default: + ASSERT(0); /* This should never happen */ + break; + } + + return (rc); +} + +/* ARGSUSED */ +static int +idm_itextbuf_add_string(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb) +{ + char *key_name; + char *value; + int rc; + + /* Start with the key name */ + key_name = nvpair_name(nvp); + textbuf_strcpy(itb, key_name); + + /* Add separator */ + textbuf_append_char(itb, '='); + + /* Add value */ + rc = nvpair_value_string(nvp, &value); + ASSERT(rc == 0); + textbuf_strcpy(itb, value); + + /* Add trailing 0x00 */ + textbuf_terminate_kvpair(itb); + + return (0); +} + + +/* ARGSUSED */ +static int +idm_itextbuf_add_boolean(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb) +{ + char *key_name; + boolean_t value; + int rc; + + /* Start with the key name */ + key_name = nvpair_name(nvp); + textbuf_strcpy(itb, key_name); + + /* Add separator */ + textbuf_append_char(itb, '='); + + /* Add value */ + rc = nvpair_value_boolean_value(nvp, &value); + ASSERT(rc == 0); + textbuf_strcpy(itb, value ? "Yes" : "No"); + + /* Add trailing 0x00 */ + textbuf_terminate_kvpair(itb); + + return (0); +} + +/* ARGSUSED */ +static int +idm_itextbuf_add_binary(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb) +{ + char *key_name; + unsigned char *value; + unsigned int len; + unsigned long n; + int rc; + + /* Start with the key name */ + key_name = nvpair_name(nvp); + textbuf_strcpy(itb, key_name); + + /* Add separator */ + textbuf_append_char(itb, '='); + + /* Add value */ + rc = nvpair_value_byte_array(nvp, &value, &len); + ASSERT(rc == 0); + + textbuf_strcpy(itb, "0x"); + + while (len > 0) { + n = *value++; + len--; + + textbuf_append_char(itb, idm_hex_to_ascii[(n >> 4) & 0xf]); + textbuf_append_char(itb, idm_hex_to_ascii[n & 0xf]); + } + + /* Add trailing 0x00 */ + textbuf_terminate_kvpair(itb); + + return (0); +} + +/* ARGSUSED */ +static int +idm_itextbuf_add_large_numerical(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb) +{ + ASSERT(0); + return (0); +} + +/* ARGSUSED */ +static int +idm_itextbuf_add_numerical(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb) +{ + char *key_name; + uint64_t value; + int rc; + char str[16]; + + /* Start with the key name */ + key_name = nvpair_name(nvp); + textbuf_strcpy(itb, key_name); + + /* Add separator */ + textbuf_append_char(itb, '='); + + /* Add value */ + rc = nvpair_value_uint64(nvp, &value); + ASSERT(rc == 0); + (void) sprintf(str, "%llu", (u_longlong_t)value); + textbuf_strcpy(itb, str); + + /* Add trailing 0x00 */ + textbuf_terminate_kvpair(itb); + + return (0); +} + +/* ARGSUSED */ +static int +idm_itextbuf_add_numeric_range(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb) +{ + ASSERT(0); + return (0); +} + +/* ARGSUSED */ +static int +idm_itextbuf_add_list_of_values(nvpair_t *nvp, + const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb) +{ + char *key_name; + nvpair_t *vchoice = NULL; + char *vchoice_string = NULL; + int rc; + + /* Start with the key name */ + key_name = nvpair_name(nvp); + textbuf_strcpy(itb, key_name); + + /* Add separator */ + textbuf_append_char(itb, '='); + + /* Add value choices */ + vchoice = idm_get_next_listvalue(nvp, NULL); + while (vchoice != NULL) { + rc = nvpair_value_string(vchoice, &vchoice_string); + ASSERT(rc == 0); + textbuf_strcpy(itb, vchoice_string); + vchoice = idm_get_next_listvalue(nvp, vchoice); + if (vchoice != NULL) { + /* Add ',' between choices */ + textbuf_append_char(itb, ','); + } + } + + /* Add trailing 0x00 */ + textbuf_terminate_kvpair(itb); + + return (0); +} + + +static void +textbuf_makeroom(idm_textbuf_t *itb, int size) +{ + char *new_mem; + int new_mem_len; + + if (itb->itb_mem == NULL) { + itb->itb_mem_len = MAX(TEXTBUF_CHUNKSIZE, size); + itb->itb_mem = kmem_alloc(itb->itb_mem_len, KM_SLEEP); + } else if ((itb->itb_offset + size) > itb->itb_mem_len) { + new_mem_len = itb->itb_mem_len + MAX(TEXTBUF_CHUNKSIZE, size); + new_mem = kmem_alloc(new_mem_len, KM_SLEEP); + bcopy(itb->itb_mem, new_mem, itb->itb_mem_len); + kmem_free(itb->itb_mem, itb->itb_mem_len); + itb->itb_mem = new_mem; + itb->itb_mem_len = new_mem_len; + } +} + +static void +textbuf_memcpy(idm_textbuf_t *itb, void *mem, int mem_len) +{ + textbuf_makeroom(itb, mem_len); + (void) memcpy(itb->itb_mem + itb->itb_offset, mem, mem_len); + itb->itb_offset += mem_len; +} + +static void +textbuf_strcpy(idm_textbuf_t *itb, char *str) +{ + textbuf_memcpy(itb, str, strlen(str)); +} + +static void +textbuf_append_char(idm_textbuf_t *itb, char c) +{ + textbuf_makeroom(itb, sizeof (char)); + *(itb->itb_mem + itb->itb_offset) = c; + itb->itb_offset++; +} + +static void +textbuf_terminate_kvpair(idm_textbuf_t *itb) +{ + textbuf_append_char(itb, '\0'); +} + +static int +idm_ascii_to_hex(char *enc_hex_byte, uint8_t *bin_val) +{ + uint8_t nibble1, nibble2; + char enc_char = *enc_hex_byte; + + if (enc_char >= '0' && enc_char <= '9') { + nibble1 = (enc_char - '0'); + } else if (enc_char >= 'A' && enc_char <= 'F') { + nibble1 = (0xA + (enc_char - 'A')); + } else if (enc_char >= 'a' && enc_char <= 'f') { + nibble1 = (0xA + (enc_char - 'a')); + } else { + return (EINVAL); + } + + enc_hex_byte++; + enc_char = *enc_hex_byte; + + if (enc_char >= '0' && enc_char <= '9') { + nibble2 = (enc_char - '0'); + } else if (enc_char >= 'A' && enc_char <= 'F') { + nibble2 = (0xA + (enc_char - 'A')); + } else if (enc_char >= 'a' && enc_char <= 'f') { + nibble2 = (0xA + (enc_char - 'a')); + } else { + return (EINVAL); + } + + *bin_val = (nibble1 << 4) | nibble2; + + return (0); +} + + +static int idm_base16_str_to_binary(char *hstr, int hstr_len, + uint8_t *binary_array, int binary_length) +{ + char tmpstr[2]; + uchar_t *binary_scan; + + binary_scan = binary_array; + + /* + * If the length of the encoded ascii hex value is a multiple + * of two then every two ascii characters correspond to a hex + * byte. If the length of the value is not a multiple of two + * then the first character is the first hex byte and then for + * the remaining of the string every two ascii characters + * correspond to a hex byte + */ + if ((hstr_len % 2) != 0) { + + tmpstr[0] = '0'; + tmpstr[1] = *hstr; + + if (idm_ascii_to_hex(tmpstr, binary_scan) != 0) { + return (EINVAL); + } + + hstr++; + binary_scan++; + } + + while (binary_scan != binary_array + binary_length) { + if (idm_ascii_to_hex(hstr, binary_scan) != 0) { + return (EINVAL); + } + + hstr += 2; + binary_scan++; + } + + return (0); +} + +static size_t +idm_strnlen(const char *str, size_t maxlen) +{ + const char *ptr; + + ptr = memchr(str, 0, maxlen); + if (ptr == NULL) + return (maxlen); + + return ((uintptr_t)ptr - (uintptr_t)str); +} + + +size_t +idm_strcspn(const char *string, const char *charset) +{ + const char *p, *q; + + for (q = string; *q != '\0'; ++q) { + for (p = charset; *p != '\0' && *p != *q; ) + p++; + if (*p != '\0') { + break; + } + } + return ((uintptr_t)q - (uintptr_t)string); +} + +/* + * We allow a list of choices to be represented as a single nvpair + * (list with one value choice), or as an nvlist with a single nvpair + * (also a list with on value choice), or as an nvlist with multiple + * nvpairs (a list with multiple value choices). This function implements + * the "get next" functionality regardless of the choice list structure. + * + * nvpair_t's that contain choices are always strings. + */ +nvpair_t * +idm_get_next_listvalue(nvpair_t *value_list, nvpair_t *curr_nvp) +{ + nvpair_t *result; + nvlist_t *nvl; + int nvrc; + data_type_t nvp_type; + + nvp_type = nvpair_type(value_list); + + switch (nvp_type) { + case DATA_TYPE_NVLIST: + nvrc = nvpair_value_nvlist(value_list, &nvl); + ASSERT(nvrc == 0); + result = nvlist_next_nvpair(nvl, curr_nvp); + break; + case DATA_TYPE_STRING: + /* Single choice */ + if (curr_nvp == NULL) { + result = value_list; + } else { + result = NULL; + } + break; + default: + ASSERT(0); /* Malformed choice list */ + result = NULL; + break; + } + + return (result); +} + +kv_status_t +idm_nvstat_to_kvstat(int nvrc) +{ + kv_status_t result; + switch (nvrc) { + case 0: + result = KV_HANDLED; + break; + case ENOMEM: + result = KV_NO_RESOURCES; + break; + case EINVAL: + result = KV_VALUE_ERROR; + break; + case EFAULT: + case ENOTSUP: + default: + result = KV_INTERNAL_ERROR; + break; + } + + return (result); +} + +void +idm_kvstat_to_error(kv_status_t kvrc, uint8_t *class, uint8_t *detail) +{ + switch (kvrc) { + case KV_HANDLED: + case KV_HANDLED_NO_TRANSIT: + *class = ISCSI_STATUS_CLASS_SUCCESS; + *detail = ISCSI_LOGIN_STATUS_ACCEPT; + break; + case KV_UNHANDLED: + case KV_TARGET_ONLY: + /* protocol error */ + *class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + *detail = ISCSI_LOGIN_STATUS_INVALID_REQUEST; + break; + case KV_VALUE_ERROR: + /* invalid value */ + *class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + *detail = ISCSI_LOGIN_STATUS_INIT_ERR; + break; + case KV_NO_RESOURCES: + /* no memory */ + *class = ISCSI_STATUS_CLASS_TARGET_ERR; + *detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + break; + case KV_MISSING_FIELDS: + /* key/value pair(s) missing */ + *class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + *detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS; + break; + case KV_AUTH_FAILED: + /* authentication failed */ + *class = ISCSI_STATUS_CLASS_INITIATOR_ERR; + *detail = ISCSI_LOGIN_STATUS_AUTH_FAILED; + break; + default: + /* target error */ + *class = ISCSI_STATUS_CLASS_TARGET_ERR; + *detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; + break; + } +} + +int +idm_nvlist_add_keyvalue(nvlist_t *nvl, + char *key, int keylen, char *value) +{ + const idm_kv_xlate_t *ikvx; + + ikvx = idm_lookup_kv_xlate(key, keylen); + + if (ikvx->ik_key_id == KI_MAX_KEY) { + return (nvlist_add_string(nvl, key, value)); + } + + return (idm_nvlist_add_kv(nvl, ikvx, value)); +} + +int +idm_nvlist_add_id(nvlist_t *nvl, iscsikey_id_t kv_id, char *value) +{ + int i; + for (i = 0; i < KI_MAX_KEY; i++) { + if (idm_kvpair_xlate[i].ik_key_id == kv_id) { + return + (idm_nvlist_add_kv(nvl, + &idm_kvpair_xlate[i], value)); + } + } + return (EFAULT); +} + +char * +idm_id_to_name(iscsikey_id_t kv_id) +{ + int i; + for (i = 0; i < KI_MAX_KEY; i++) { + if (idm_kvpair_xlate[i].ik_key_id == kv_id) { + return (idm_kvpair_xlate[i].ik_key_name); + } + } + return (NULL); +} + +/* + * return the value in a buffer that must be freed by the caller + */ +char * +idm_nvpair_value_to_textbuf(nvpair_t *nvp) +{ + int rv, len; + idm_textbuf_t itb; + char *str; + + bzero(&itb, sizeof (itb)); + rv = idm_itextbuf_add_nvpair(nvp, &itb); + if (rv != 0) + return (NULL); + str = kmem_alloc(itb.itb_mem_len, KM_SLEEP); + len = idm_strcspn(itb.itb_mem, "="); + if (len > strlen(itb.itb_mem)) { + kmem_free(itb.itb_mem, itb.itb_mem_len); + return (NULL); + } + (void) strcpy(str, &itb.itb_mem[len+1]); + /* free the allocation done in idm_textbuf_add_nvpair */ + kmem_free(itb.itb_mem, itb.itb_mem_len); + return (str); +} + +/* + * build an iscsi text buffer - the memory gets freed in + * idm_itextbuf_free + */ +void * +idm_nvlist_to_itextbuf(nvlist_t *nvl) +{ + idm_textbuf_t *itb; + char *textbuf; + int validlen, textbuflen; + + if (idm_nvlist_to_textbuf(nvl, &textbuf, &textbuflen, + &validlen) != IDM_STATUS_SUCCESS) { + return (NULL); + } + itb = kmem_zalloc(sizeof (idm_textbuf_t), KM_SLEEP); + ASSERT(itb != NULL); + itb->itb_mem = textbuf; + itb->itb_mem_len = textbuflen; + itb->itb_offset = validlen; + return ((void *)itb); +} + +/* + * Update the pdu data up to min of max_xfer_len or data left. + * The first call to this routine should send + * a NULL bufptr. Subsequent calls send in the buffer returned. + * Call this routine until the string returned is NULL + */ +char * +idm_pdu_init_text_data(idm_pdu_t *pdu, void *arg, + int max_xfer_len, char *bufptr, int *transit) +{ + char *start_ptr, *end_ptr, *ptr; + idm_textbuf_t *itb = arg; + iscsi_hdr_t *ihp = pdu->isp_hdr; + int send = 0; + + ASSERT(itb != NULL); + ASSERT(pdu != NULL); + ASSERT(transit != NULL); + if (bufptr == NULL) { + /* first call - check the length */ + if (itb->itb_offset <= max_xfer_len) { + idm_pdu_init_data(pdu, (uint8_t *)itb->itb_mem, + itb->itb_offset); + ihp->flags &= ~ISCSI_FLAG_TEXT_CONTINUE; + *transit = 1; + return (NULL); + } + /* we have more data than will fit in one pdu */ + start_ptr = itb->itb_mem; + end_ptr = &itb->itb_mem[max_xfer_len - 1]; + + } else { + if ((uintptr_t)&itb->itb_mem[itb->itb_offset] - + (uintptr_t)bufptr <= max_xfer_len) { + idm_pdu_init_data(pdu, (uint8_t *)bufptr, + (uintptr_t)&itb->itb_mem[itb->itb_offset] - + (uintptr_t)bufptr); + ihp->flags &= ~ISCSI_FLAG_TEXT_CONTINUE; + *transit = 1; + return (NULL); + } + /* we have more data then will fit in one pdu */ + start_ptr = bufptr; + end_ptr = &bufptr[max_xfer_len - 1]; + } + /* break after key, after =, after the value or after '\0' */ + ptr = end_ptr; + if (end_ptr + 1 <= &itb->itb_mem[itb->itb_offset]) { + /* if next char is an '=' or '\0' send it */ + if (*(end_ptr + 1) == '=' || *(end_ptr + 1) == '\0') { + send = 1; + } + } + if (!send) { + while (*ptr != '\0' && *ptr != '=' && ptr != start_ptr) { + ptr--; + } + } + idm_pdu_init_data(pdu, (uint8_t *)start_ptr, + ((uintptr_t)ptr - (uintptr_t)start_ptr) + 1); + ihp->flags |= ISCSI_FLAG_TEXT_CONTINUE; + *transit = 0; + return (++ptr); +} + +void +idm_itextbuf_free(void *arg) +{ + idm_textbuf_t *itb = arg; + ASSERT(itb != NULL); + kmem_free(itb->itb_mem, itb->itb_mem_len); + kmem_free(itb, sizeof (idm_textbuf_t)); +} + +/* + * Allocate an nvlist and poputlate with key=value from the pdu list. + * NOTE: caller must free the list + */ +idm_status_t +idm_pdu_list_to_nvlist(list_t *pdu_list, nvlist_t **nvlist, + uint8_t *error_detail) +{ + idm_pdu_t *pdu, *next_pdu; + boolean_t split_kv = B_FALSE; + char *textbuf, *leftover_textbuf = NULL; + int textbuflen, leftover_textbuflen = 0; + char *split_kvbuf; + int split_kvbuflen, cont_fraglen; + iscsi_login_hdr_t *lh; + int rc; + int ret = IDM_STATUS_SUCCESS; + + *error_detail = ISCSI_LOGIN_STATUS_ACCEPT; + /* Allocate a new nvlist for request key/value pairs */ + rc = nvlist_alloc(nvlist, NV_UNIQUE_NAME, + KM_NOSLEEP); + if (rc != 0) { + *error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + ret = IDM_STATUS_FAIL; + goto cleanup; + } + + /* + * A login request can be split across multiple PDU's. The state + * machine has collected all the PDU's that make up this login request + * and assembled them on the "icl_pdu_list" queue. Process each PDU + * and convert the text keywords to nvlist form. + */ + pdu = list_head(pdu_list); + while (pdu != NULL) { + next_pdu = list_next(pdu_list, pdu); + + lh = (iscsi_login_hdr_t *)pdu->isp_hdr; + + textbuf = (char *)pdu->isp_data; + textbuflen = pdu->isp_datalen; + if (textbuflen == 0) { + /* This shouldn't really happen but it could.. */ + list_remove(pdu_list, pdu); + idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); + pdu = next_pdu; + continue; + } + + /* + * If we encountered a split key-value pair on the last + * PDU then handle it now by grabbing the remainder of the + * key-value pair from the next PDU and splicing them + * together. Obviously on the first PDU this will never + * happen. + */ + if (split_kv) { + cont_fraglen = idm_textbuf_to_firstfraglen(textbuf, + textbuflen); + if (cont_fraglen == pdu->isp_datalen) { + /* + * This key-value pair spans more than two + * PDU's. We don't handle this. + */ + *error_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; + ret = IDM_STATUS_FAIL; + goto cleanup; + } + + split_kvbuflen = leftover_textbuflen + cont_fraglen; + split_kvbuf = kmem_alloc(split_kvbuflen, KM_NOSLEEP); + if (split_kvbuf == NULL) { + *error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES; + ret = IDM_STATUS_FAIL; + goto cleanup; + } + + bcopy(leftover_textbuf, split_kvbuf, + leftover_textbuflen); + bcopy(textbuf, + (uint8_t *)split_kvbuf + leftover_textbuflen, + cont_fraglen); + + + if (idm_textbuf_to_nvlist(*nvlist, + &split_kvbuf, &split_kvbuflen) != 0) { + /* + * Need to handle E2BIG case, indicating that + * a key-value pair is split across multiple + * PDU's. + */ + kmem_free(split_kvbuf, split_kvbuflen); + + *error_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; + ret = IDM_STATUS_FAIL; + goto cleanup; + } + + ASSERT(split_kvbuflen != NULL); + kmem_free(split_kvbuf, split_kvbuflen); + + /* Now handle the remainder of the PDU as normal */ + textbuf += (cont_fraglen + 1); + textbuflen -= (cont_fraglen + 1); + } + + /* + * Convert each key-value pair in the text buffer to nvlist + * format. If the list has already been created the nvpair + * elements will be added on to the existing list. Otherwise + * a new nvlist will be created. + */ + if (idm_textbuf_to_nvlist(*nvlist, + &textbuf, &textbuflen) != 0) { + + *error_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR; + ret = IDM_STATUS_FAIL; + goto cleanup; + } + + ASSERT( + ((lh->flags & ISCSI_FLAG_LOGIN_CONTINUE) && + (next_pdu != NULL)) || + (!(lh->flags & ISCSI_FLAG_LOGIN_CONTINUE) && + (next_pdu == NULL))); + + if ((lh->flags & ISCSI_FLAG_LOGIN_CONTINUE) & + (textbuflen != 0)) { + /* + * Key-value pair is split over two PDU's. We + * assume it willl never be split over more than + * two PDU's. + */ + split_kv = B_TRUE; + leftover_textbuf = textbuf; + leftover_textbuflen = textbuflen; + } else { + split_kv = B_FALSE; + if (textbuflen != 0) { + /* + * Incomplete keyword but no additional + * PDU's. This is a malformed login + * request. + */ + *error_detail = + ISCSI_LOGIN_STATUS_INVALID_REQUEST; + ret = IDM_STATUS_FAIL; + goto cleanup; + } + } + + list_remove(pdu_list, pdu); + idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); + pdu = next_pdu; + } + +cleanup: + + /* + * Free any remaining PDUs on the list. This will only + * happen if there were errors encountered during + * processing of the textbuf. + */ + pdu = list_head(pdu_list); + while (pdu != NULL) { + next_pdu = list_next(pdu_list, pdu); + list_remove(pdu_list, pdu); + idm_pdu_complete(pdu, IDM_STATUS_SUCCESS); + pdu = next_pdu; + } + + /* + * If there were no errors, we have a complete nvlist representing + * all the iSCSI key-value pairs in the login request PDU's + * that make up this request. + */ + return (ret); +} + +/* + * idm_strtoull + * + * Since the kernel only provides ddi_strtoul (not the ddi_strtoull that we + * would like to use) we need our own conversion function to convert + * string integer representations to 64-bit integers. ddi_strtoul doesn't + * work well for us on 32-bit systems. This code is shamelessly ripped + * from ddi_strtol.c. Eventually we should push this back into the DDI + * so that we don't need our own version anymore. + */ + +#define isalnum(ch) (isalpha(ch) || isdigit(ch)) +#define isalpha(ch) (isupper(ch) || islower(ch)) +#define isdigit(ch) ((ch) >= '0' && (ch) <= '9') +#define islower(ch) ((ch) >= 'a' && (ch) <= 'z') +#define isspace(ch) (((ch) == ' ') || ((ch) == '\r') || ((ch) == '\n') || \ + ((ch) == '\t') || ((ch) == '\f')) +#define isupper(ch) ((ch) >= 'A' && (ch) <= 'Z') +#define isxdigit(ch) (isdigit(ch) || ((ch) >= 'a' && (ch) <= 'f') || \ + ((ch) >= 'A' && (ch) <= 'F')) + +#define DIGIT(x) \ + (isdigit(x) ? (x) - '0' : islower(x) ? (x) + 10 - 'a' : (x) + 10 - 'A') + +#define MBASE ('z' - 'a' + 1 + 10) + +#define lisalnum(x) \ + (isdigit(x) || ((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z')) + +#ifndef ULLONG_MAX +#define ULLONG_MAX 18446744073709551615ULL +#endif + +int +idm_strtoull(const char *str, char **nptr, int base, + unsigned long long *result) +{ + unsigned long long val; + int c; + int xx; + unsigned long long multmax; + int neg = 0; + const char **ptr = (const char **)nptr; + const unsigned char *ustr = (const unsigned char *)str; + + if (ptr != (const char **)0) + *ptr = (char *)ustr; /* in case no number is formed */ + if (base < 0 || base > MBASE || base == 1) { + /* base is invalid -- should be a fatal error */ + return (EINVAL); + } + if (!isalnum(c = *ustr)) { + while (isspace(c)) + c = *++ustr; + switch (c) { + case '-': + neg++; + /* FALLTHROUGH */ + case '+': + c = *++ustr; + } + } + if (base == 0) + if (c != '0') + base = 10; + else if (ustr[1] == 'x' || ustr[1] == 'X') + base = 16; + else + base = 8; + /* + * for any base > 10, the digits incrementally following + * 9 are assumed to be "abc...z" or "ABC...Z" + */ + if (!lisalnum(c) || (xx = DIGIT(c)) >= base) + return (EINVAL); /* no number formed */ + if (base == 16 && c == '0' && (ustr[1] == 'x' || ustr[1] == 'X') && + isxdigit(ustr[2])) + c = *(ustr += 2); /* skip over leading "0x" or "0X" */ + + multmax = ULLONG_MAX / (unsigned long long)base; + val = DIGIT(c); + for (c = *++ustr; lisalnum(c) && (xx = DIGIT(c)) < base; ) { + if (val > multmax) + goto overflow; + val *= base; + if (ULONG_MAX - val < xx) + goto overflow; + val += xx; + c = *++ustr; + } + if (ptr != (const char **)0) + *ptr = (char *)ustr; + *result = neg ? -val : val; + return (0); + +overflow: + for (c = *++ustr; lisalnum(c) && (xx = DIGIT(c)) < base; ) + c = *++ustr; + if (ptr != (const char **)0) + *ptr = (char *)ustr; + return (ERANGE); +} diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile index 725a56854c39..d0d808d45935 100644 --- a/usr/src/uts/common/sys/Makefile +++ b/usr/src/uts/common/sys/Makefile @@ -717,6 +717,22 @@ IBMGTHDRS= \ IBDHDRS= \ ibd.h +IDMHDRS= \ + idm.h \ + idm_impl.h \ + idm_so.h \ + idm_text.h \ + idm_transport.h \ + idm_conn_sm.h + +ISCSITHDRS= \ + radius_packet.h \ + radius_protocol.h \ + chap.h \ + isns_protocol.h \ + iscsi_if.h \ + iscsit_common.h + ISOHDRS= \ signal_iso.h @@ -1087,6 +1103,9 @@ CHECKHDRS= \ $(IBMFHDRS:%.h=ib/mgt/ibmf/%.check) \ $(TAVORHDRS:%.h=ib/adapters/tavor/%.check) \ $(HERMONHDRS:%.h=ib/adapters/hermon/%.check) \ + $(IDMHDRS:%.h=idm/%.check) \ + $(ISCSIHDRS:%.h=iscsi/%.check) \ + $(ISCSITHDRS:%.h=iscsit/%.check) \ $(ISOHDRS:%.h=iso/%.check) \ $(FMHDRS:%.h=fm/%.check) \ $(FMFSHDRS:%.h=fm/fs/%.check) \ @@ -1129,6 +1148,9 @@ CHECKHDRS= \ $(ROOTCRYPTOHDRS) \ $(ROOTDCAMHDRS) \ $(ROOTISOHDRS) \ + $(ROOTIDMHDRS) \ + $(ROOTISCSIHDRS) \ + $(ROOTISCSITHDRS) \ $(ROOTFC4HDRS) \ $(ROOTFCHDRS) \ $(ROOTFCIMPLHDRS) \ @@ -1183,6 +1205,9 @@ install_h: \ $(ROOTCRYPTOHDRS) \ $(ROOTDCAMHDRS) \ $(ROOTISOHDRS) \ + $(ROOTIDMHDRS) \ + $(ROOTISCSIHDRS) \ + $(ROOTISCSITHDRS) \ $(ROOTFC4HDRS) \ $(ROOTFCHDRS) \ $(ROOTFCIMPLHDRS) \ diff --git a/usr/src/uts/common/sys/Makefile.syshdrs b/usr/src/uts/common/sys/Makefile.syshdrs index c29c951933d8..bc18a6e04276 100644 --- a/usr/src/uts/common/sys/Makefile.syshdrs +++ b/usr/src/uts/common/sys/Makefile.syshdrs @@ -74,6 +74,12 @@ ib/adapters/tavor/%.check: ib/adapters/tavor/%.h ib/adapters/hermon/%.check: ib/adapters/hermon/%.h $(DOT_H_CHECK) +idm/%.check: idm/%.h + $(DOT_H_CHECK) + +iscsit/%.check: iscsit/%.h + $(DOT_H_CHECK) + lvm/%.check: lvm/%.h $(DOT_H_CHECK) @@ -180,6 +186,8 @@ ROOTDIRS= \ $(ROOTDIR)/ib/mgt/ibmf \ $(ROOTDIR)/ib/ibnex \ $(ROOTDIR)/ib/clients/ibd \ + $(ROOTDIR)/idm \ + $(ROOTDIR)/iscsit \ $(ROOTDIR)/lvm \ $(ROOTDIR)/pcmcia \ $(ROOTDIR)/scsi \ @@ -227,6 +235,9 @@ ROOTIBMFHDRS= $(IBMFHDRS:%=$(ROOTDIR)/ib/mgt/ibmf/%) ROOTTAVORHDRS= $(TAVORHDRS:%=$(ROOTDIR)/ib/adapters/tavor/%) ROOTHERMONHDRS= $(HERMONHDRS:%=$(ROOTDIR)/ib/adapters/hermon/%) +ROOTIDMHDRS= $(IDMHDRS:%=$(ROOTDIR)/idm/%) +ROOTISCSITHDRS= $(ISCSITHDRS:%=$(ROOTDIR)/iscsit/%) + ROOTISOHDRS= $(ISOHDRS:%=$(ROOTDIR)/iso/%) ROOTFMHDRS= $(FMHDRS:%=$(ROOTDIR)/fm/%) diff --git a/usr/src/uts/common/sys/idm/idm.h b/usr/src/uts/common/sys/idm/idm.h new file mode 100644 index 000000000000..7d97084c103b --- /dev/null +++ b/usr/src/uts/common/sys/idm/idm.h @@ -0,0 +1,445 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _IDM_H +#define _IDM_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + IDM_STATUS_SUCCESS = 0, + IDM_STATUS_FAIL, + IDM_STATUS_NORESOURCES, + IDM_STATUS_REJECT, + IDM_STATUS_IO, + IDM_STATUS_ABORTED, + IDM_STATUS_SUSPENDED, + IDM_STATUS_HEADER_DIGEST, + IDM_STATUS_DATA_DIGEST, + IDM_STATUS_PROTOCOL_ERROR +} idm_status_t; + +typedef enum { + CN_CONNECT_ACCEPT = 1, /* Target only */ + CN_LOGIN_FAIL, /* Target only */ + CN_READY_FOR_LOGIN, /* Initiator only */ + CN_FFP_ENABLED, + CN_FFP_DISABLED, + CN_CONNECT_LOST, + CN_CONNECT_DESTROY +} idm_client_notify_t; + +typedef enum { + FD_CONN_FAIL, + FD_CONN_LOGOUT, + FD_SESS_LOGOUT +} idm_ffp_disable_t; + +typedef enum { + AT_INTERNAL_SUSPEND, + AT_INTERNAL_ABORT, + AT_TASK_MGMT_ABORT +} idm_abort_type_t; + +typedef enum { + TASK_IDLE, + TASK_ACTIVE, + TASK_SUSPENDING, + TASK_SUSPENDED, + TASK_ABORTING, + TASK_ABORTED, + TASK_COMPLETE +} idm_task_state_t; + +typedef enum { + KV_HANDLED = 0, + KV_HANDLED_NO_TRANSIT, + KV_UNHANDLED, + KV_TARGET_ONLY, + KV_NO_RESOURCES, + KV_INTERNAL_ERROR, + KV_VALUE_ERROR, + KV_MISSING_FIELDS, + KV_AUTH_FAILED +} kv_status_t; + +/* + * Request structures + */ + +/* Defined in idm_impl.h */ +struct idm_conn_s; +struct idm_svc_s; +struct idm_buf_s; +struct idm_pdu_s; +struct idm_task_s; + +typedef idm_status_t (idm_client_notify_cb_t)( + struct idm_conn_s *ic, idm_client_notify_t cn, uintptr_t data); + +typedef void (idm_rx_pdu_cb_t)(struct idm_conn_s *ic, struct idm_pdu_s *pdu); + +typedef void (idm_rx_pdu_error_cb_t)(struct idm_conn_s *ic, + struct idm_pdu_s *pdu, idm_status_t status); + +typedef void (idm_buf_cb_t)(struct idm_buf_s *idb, idm_status_t status); + +typedef void (idm_pdu_cb_t)(struct idm_pdu_s *pdu, idm_status_t status); + +typedef void (idm_task_cb_t)(struct idm_task_s *task, idm_status_t status); + +typedef void (idm_build_hdr_cb_t)( + struct idm_task_s *task, struct idm_pdu_s *pdu, uint8_t opcode); + +typedef union idm_sockaddr { + struct sockaddr sin; + struct sockaddr_in sin4; + struct sockaddr_in6 sin6; +} idm_sockaddr_t; + +#define SIZEOF_SOCKADDR(so) \ + ((so)->sa_family == AF_INET ? \ + sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6)) + +typedef struct { + idm_rx_pdu_cb_t *icb_rx_scsi_cmd; + idm_rx_pdu_cb_t *icb_rx_scsi_rsp; + idm_rx_pdu_cb_t *icb_rx_misc; + idm_rx_pdu_error_cb_t *icb_rx_error; + idm_task_cb_t *icb_task_aborted; + idm_client_notify_cb_t *icb_client_notify; + idm_build_hdr_cb_t *icb_build_hdr; +} idm_conn_ops_t; + +typedef struct { + int cr_domain; + int cr_type; + int cr_protocol; + boolean_t cr_bound; + idm_sockaddr_t cr_bound_addr; + idm_sockaddr_t cr_ini_dst_addr; + ldi_ident_t cr_li; + idm_conn_ops_t icr_conn_ops; +} idm_conn_req_t; + +typedef struct { + uint16_t sr_port; + ldi_ident_t sr_li; + idm_conn_ops_t sr_conn_ops; +} idm_svc_req_t; + + +/* This is not how other networking code handles this */ +typedef struct { + union { + struct in_addr in4; + struct in6_addr in6; + } i_addr; + /* i_insize determines which is valid in the union above */ + int i_insize; +} idm_ipaddr_t; + +typedef struct { + idm_ipaddr_t a_addr; + uint32_t a_port, + a_oid; +} idm_addr_t; + +typedef struct { + uint32_t al_vers, /* In */ + al_oid; /* In */ + uint32_t al_in_cnt; /* In */ + uint32_t al_out_cnt; /* Out */ + uint32_t al_tpgt; /* Out */ + idm_addr_t al_addrs[1]; /* Out */ +} idm_addr_list_t; + +/* + * State machine auditing + */ + +#define SM_AUDIT_BUF_MAX_REC 32 + +typedef enum { + SAR_UNDEFINED = 0, + SAR_STATE_EVENT, + SAR_STATE_CHANGE +} sm_audit_record_type_t; + +typedef enum { + SAS_UNDEFINED = 0, + SAS_IDM_CONN, + SAS_IDM_TASK, + SAS_ISCSIT_TGT, + SAS_ISCSIT_SESS, + SAS_ISCSIT_LOGIN +} sm_audit_sm_type_t; + +typedef struct { + timespec_t sar_timestamp; + sm_audit_sm_type_t sar_sm_type; + sm_audit_record_type_t sar_type; + int sar_state; + int sar_new_state; /* Only for SAR_STATE_CHANGE */ + int sar_event; /* Only for SAR_STATE_EVENT */ + uintptr_t sar_event_info; /* Only for SAR_STATE_EVENT */ +} sm_audit_record_t; + +typedef struct { + int sab_index; + int sab_max_index; + sm_audit_record_t sab_records[SM_AUDIT_BUF_MAX_REC]; +} sm_audit_buf_t; + +extern boolean_t idm_sm_logging; +extern boolean_t idm_conn_logging; +extern boolean_t idm_svc_logging; + +#define IDM_SM_LOG if (idm_sm_logging) cmn_err +#define IDM_CONN_LOG if (idm_conn_logging) cmn_err +#define IDM_SVC_LOG if (idm_svc_logging) cmn_err + +void idm_sm_audit_init(sm_audit_buf_t *audit_buf); + +void idm_sm_audit_event(sm_audit_buf_t *audit_buf, + sm_audit_sm_type_t sm_type, + int state, int event, uintptr_t event_info); + +void idm_sm_audit_state_change(sm_audit_buf_t *audit_buf, + sm_audit_sm_type_t sm_type, int state, int new_state); + + +#include +#include +#include +#include +#include +#include + +/* + * iSCSI Initiator Services + */ + +idm_status_t +idm_ini_conn_create(idm_conn_req_t *cr, idm_conn_t **new_con); + +idm_status_t +idm_ini_conn_connect(idm_conn_t *ic); + +void +idm_ini_conn_disconnect(idm_conn_t *ic); + +void +idm_ini_conn_destroy(idm_conn_t *ic); + +/* + * iSCSI Target Services + */ + +idm_status_t +idm_tgt_svc_create(idm_svc_req_t *sr, idm_svc_t **new_svc); + +idm_status_t +idm_tgt_svc_online(idm_svc_t *is); + +void +idm_tgt_svc_offline(idm_svc_t *is); + +void +idm_tgt_svc_destroy(idm_svc_t *is); + +void +idm_tgt_svc_destroy_if_unref(idm_svc_t *is); + +idm_svc_t * +idm_tgt_svc_lookup(uint16_t port); + +void +idm_tgt_svc_hold(idm_svc_t *is); + +void +idm_tgt_svc_rele_and_destroy(idm_svc_t *is); + +idm_status_t +idm_tgt_conn_accept(idm_conn_t *ic); + +void +idm_tgt_conn_reject(idm_conn_t *ic); + +void +idm_conn_hold(idm_conn_t *ic); + +void +idm_conn_rele(idm_conn_t *ic); + +/* + * Target data transfer services + */ +idm_status_t +idm_buf_tx_to_ini(idm_task_t *idt, idm_buf_t *idb, + uint32_t offset, uint32_t xfer_length, + idm_buf_cb_t idb_buf_cb, void *cb_arg); + +idm_status_t +idm_buf_rx_from_ini(idm_task_t *idt, idm_buf_t *idb, + uint32_t offset, uint32_t xfer_length, + idm_buf_cb_t idb_buf_cb, void *cb_arg); + +void +idm_buf_tx_to_ini_done(idm_task_t *idt, idm_buf_t *idb, idm_status_t status); + +void +idm_buf_rx_from_ini_done(idm_task_t *idt, idm_buf_t *idb, idm_status_t status); + +/* + * Shared Initiator/Target Services + */ +kv_status_t +idm_negotiate_key_values(idm_conn_t *ic, nvlist_t *request_nvl, + nvlist_t *response_nvl, nvlist_t *negotiated_nvl); + +idm_status_t +idm_notice_key_values(idm_conn_t *ic, nvlist_t *negotiated_nvl); + +/* + * Buffer services + */ + +idm_buf_t * +idm_buf_alloc(idm_conn_t *ic, void *bufptr, uint64_t buflen); + +void +idm_buf_free(idm_buf_t *idb); + +void +idm_buf_bind_in(idm_task_t *idt, idm_buf_t *buf); + +void +idm_buf_bind_out(idm_task_t *idt, idm_buf_t *buf); + +void +idm_buf_unbind_in(idm_task_t *idt, idm_buf_t *buf); + +void +idm_buf_unbind_out(idm_task_t *idt, idm_buf_t *buf); + +idm_buf_t * +idm_buf_find(void *lbuf, size_t data_offset); + +/* + * Task services + */ +idm_task_t * +idm_task_alloc(idm_conn_t *ic); + +void +idm_task_start(idm_task_t *idt, uintptr_t handle); + +void +idm_task_abort(idm_conn_t *ic, idm_task_t *idt, idm_abort_type_t abort_type); + +void +idm_task_cleanup(idm_task_t *idt); + +void +idm_task_done(idm_task_t *idt); + +void +idm_task_free(idm_task_t *idt); + +idm_task_t * +idm_task_find(idm_conn_t *ic, uint32_t itt, uint32_t ttt); + +void * +idm_task_find_by_handle(idm_conn_t *ic, uintptr_t handle); + +void +idm_task_hold(idm_task_t *idt); + +void +idm_task_rele(idm_task_t *idt); + +/* + * PDU Services + */ + +idm_pdu_t * +idm_pdu_alloc(uint_t hdrlen, uint_t datalen); + +void +idm_pdu_free(idm_pdu_t *pdu); + +void +idm_pdu_init(idm_pdu_t *pdu, idm_conn_t *ic, void *private, idm_pdu_cb_t *cb); + +void +idm_pdu_init_hdr(idm_pdu_t *pdu, uint8_t *hdr, uint_t hdrlen); + +void +idm_pdu_init_data(idm_pdu_t *pdu, uint8_t *data, uint_t datalen); + +void +idm_pdu_complete(idm_pdu_t *pdu, idm_status_t status); + +void +idm_pdu_tx(idm_pdu_t *pdu); + +/* + * Object reference tracking + */ + +void +idm_refcnt_init(idm_refcnt_t *refcnt, void *referenced_obj); + +void +idm_refcnt_destroy(idm_refcnt_t *refcnt); + +void +idm_refcnt_reset(idm_refcnt_t *refcnt); + +void +idm_refcnt_hold(idm_refcnt_t *refcnt); + +void +idm_refcnt_rele(idm_refcnt_t *refcnt); + +void +idm_refcnt_rele_and_destroy(idm_refcnt_t *refcnt, idm_refcnt_cb_t *cb_func); + +void +idm_refcnt_wait_ref(idm_refcnt_t *refcnt); + +void +idm_refcnt_async_wait_ref(idm_refcnt_t *refcnt, idm_refcnt_cb_t *cb_func); + + +#ifdef __cplusplus +} +#endif + +#endif /* _IDM_H */ diff --git a/usr/src/uts/common/sys/idm/idm_conn_sm.h b/usr/src/uts/common/sys/idm/idm_conn_sm.h new file mode 100644 index 000000000000..8d7ee745ed12 --- /dev/null +++ b/usr/src/uts/common/sys/idm/idm_conn_sm.h @@ -0,0 +1,269 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#ifndef _IDM_CONN_SM_H_ +#define _IDM_CONN_SM_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * IDM connection state machine events. Most events get generated internally + * either by the state machine or by the IDM TX/RX code. For example when IDM + * receives a login request for a target connectionit will generate a + * CE_LOGIN_RCV event. Similarly when the target sends a successful login + * response IDM generate a "CE_LOGIN_SUCCESS_SND" event. The following + * events are not detected on the TX/RX path and must be generated explicitly + * by the client when appropriate: + * + * CE_LOGOUT_OTHER_CONN_RCV + * CE_ASYNC_DROP_CONN_RCV (Only because the message may be received on + * a different connection from the connection being dropped) + * CE_ASYNC_DROP_ALL_CONN_RCV + * CE_LOGOUT_OTHER_CONN_SND + * CE_ASYNC_DROP_ALL_CONN_SND + * + * The following events might occur in any state since they are driven + * by the PDU's that IDM receives: + * + * CE_LOGIN_RCV + * CE_LOGIN_SUCCESS_RCV + * CE_LOGIN_FAIL_RCV + * CE_LOGOUT_SUCCESS_RCV + * CE_LOGOUT_FAIL_RCV + * CE_ASYNC_LOGOUT_RCV + * CE_MISC_RCV + * CE_RX_PROTOCOL_ERROR + */ + +#define IDM_LOGIN_SECONDS 20 +#define IDM_LOGOUT_SECONDS 20 +#define IDM_CLEANUP_SECONDS 0 + +/* Update idm_ce_name table whenever connection events are modified */ +typedef enum { + CE_UNDEFINED = 0, + + /* Initiator events */ + CE_CONNECT_REQ, + CE_CONNECT_FAIL, + CE_CONNECT_SUCCESS, + CE_LOGIN_SND, + CE_LOGIN_SUCCESS_RCV, + CE_LOGIN_FAIL_RCV, + CE_LOGOUT_THIS_CONN_SND, + CE_LOGOUT_OTHER_CONN_SND, + CE_LOGOUT_SESSION_SND, + CE_LOGOUT_SUCCESS_RCV, + CE_LOGOUT_FAIL_RCV, + CE_ASYNC_LOGOUT_RCV, + CE_ASYNC_DROP_CONN_RCV, + CE_ASYNC_DROP_ALL_CONN_RCV, + + /* Target events */ + CE_CONNECT_ACCEPT, + CE_CONNECT_REJECT, + CE_LOGIN_RCV, + CE_LOGIN_TIMEOUT, + CE_LOGIN_SUCCESS_SND, + CE_LOGIN_FAIL_SND, + CE_LOGIN_FAIL_SND_DONE, + CE_LOGOUT_THIS_CONN_RCV, + CE_LOGOUT_OTHER_CONN_RCV, + CE_LOGOUT_SESSION_RCV, + CE_LOGOUT_SUCCESS_SND, + CE_LOGOUT_SUCCESS_SND_DONE, + CE_LOGOUT_FAIL_SND, + CE_LOGOUT_FAIL_SND_DONE, + CE_CLEANUP_TIMEOUT, + CE_ASYNC_LOGOUT_SND, + CE_ASYNC_DROP_CONN_SND, + CE_ASYNC_DROP_ALL_CONN_SND, + CE_LOGOUT_TIMEOUT, + + /* Common events */ + CE_TRANSPORT_FAIL, + CE_MISC_TX, + CE_TX_PROTOCOL_ERROR, + CE_MISC_RX, + CE_RX_PROTOCOL_ERROR, + CE_LOGOUT_SESSION_SUCCESS, + CE_CONN_REINSTATE, + CE_CONN_REINSTATE_SUCCESS, + CE_CONN_REINSTATE_FAIL, + CE_ENABLE_DM_SUCCESS, + CE_ENABLE_DM_FAIL, + + /* Add new events above CE_MAX_EVENT */ + CE_MAX_EVENT +} idm_conn_event_t; + +#ifdef IDM_CONN_SM_STRINGS +/* An array of event text values, for use in logging events */ +static const char *idm_ce_name[CE_MAX_EVENT+1] = { + "CE_UNDEFINED", + "CE_CONNECT_REQ", + "CE_CONNECT_FAIL", + "CE_CONNECT_SUCCESS", + "CE_LOGIN_SND", + "CE_LOGIN_SUCCESS_RCV", + "CE_LOGIN_FAIL_RCV", + "CE_LOGOUT_THIS_CONN_SND", + "CE_LOGOUT_OTHER_CONN_SND", + "CE_LOGOUT_SESSION_SND", + "CE_LOGOUT_SUCCESS_RCV", + "CE_LOGOUT_FAIL_RCV", + "CE_ASYNC_LOGOUT_RCV", + "CE_ASYNC_DROP_CONN_RCV", + "CE_ASYNC_DROP_ALL_CONN_RCV", + "CE_CONNECT_ACCEPT", + "CE_CONNECT_REJECT", + "CE_LOGIN_RCV", + "CE_LOGIN_TIMEOUT", + "CE_LOGIN_SUCCESS_SND", + "CE_LOGIN_FAIL_SND", + "CE_LOGIN_FAIL_SND_DONE", + "CE_LOGOUT_THIS_CONN_RCV", + "CE_LOGOUT_OTHER_CONN_RCV", + "CE_LOGOUT_SESSION_RCV", + "CE_LOGOUT_SUCCESS_SND", + "CE_LOGOUT_SUCCESS_SND_DONE", + "CE_LOGOUT_FAIL_SND", + "CE_LOGOUT_FAIL_SND_DONE", + "CE_CLEANUP_TIMEOUT", + "CE_ASYNC_LOGOUT_SND", + "CE_ASYNC_DROP_CONN_SND", + "CE_ASYNC_DROP_ALL_CONN_SND", + "CE_LOGOUT_TIMEOUT", + "CE_TRANSPORT_FAIL", + "CE_MISC_TX", + "CE_TX_PROTOCOL_ERROR", + "CE_MISC_RX", + "CE_RX_PROTOCOL_ERROR", + "CE_LOGOUT_SESSION_SUCCESS", + "CE_CONN_REINSTATE", + "CE_CONN_REINSTATE_SUCCESS", + "CE_CONN_REINSTATE_FAIL", + "CE_ENABLE_DM_SUCCESS", + "CE_ENABLE_DM_FAIL", + "CE_MAX_EVENT" +}; +#endif + +/* Update idm_cs_name table whenever connection states are modified */ +typedef enum { + CS_S0_UNDEFINED = 0, + + CS_S1_FREE, + CS_S2_XPT_WAIT, + CS_S3_XPT_UP, + CS_S4_IN_LOGIN, + CS_S5_LOGGED_IN, + CS_S6_IN_LOGOUT, + CS_S7_LOGOUT_REQ, + CS_S8_CLEANUP, + CS_S9_INIT_ERROR, + CS_S10_IN_CLEANUP, + CS_S11_COMPLETE, + CS_S12_ENABLE_DM, + + /* Add new connection states above CS_MAX_STATE */ + CS_MAX_STATE +} idm_conn_state_t; + +#ifdef IDM_CONN_SM_STRINGS +/* An array of state text values, for use in logging state transitions */ +static const char *idm_cs_name[CS_MAX_STATE+1] = { + "CS_S0_UNDEFINED", + "CS_S1_FREE", + "CS_S2_XPT_WAIT", + "CS_S3_XPT_UP", + "CS_S4_IN_LOGIN", + "CS_S5_LOGGED_IN", + "CS_S6_IN_LOGOUT", + "CS_S7_LOGOUT_REQ", + "CS_S8_CLEANUP", + "CS_S9_INIT_ERROR", + "CS_S10_IN_CLEANUP", + "CS_S11_COMPLETE", + "CS_S12_ENABLE_DM", + "CS_MAX_STATE" +}; +#endif + +typedef enum { + CT_NONE = 0, + CT_RX_PDU, + CT_TX_PDU +} idm_pdu_event_type_t; + +typedef enum { + CA_TX_PROTOCOL_ERROR, /* Send "protocol error" to state machine */ + CA_RX_PROTOCOL_ERROR, /* Send "protocol error" to state machine */ + CA_FORWARD, /* State machine event and foward to client */ + CA_DROP /* Drop PDU */ +} idm_pdu_event_action_t; + +typedef struct { + struct idm_conn_s *iec_ic; + idm_conn_event_t iec_event; + uintptr_t iec_info; + idm_pdu_event_type_t iec_pdu_event_type; +} idm_conn_event_ctx_t; + +idm_status_t +idm_conn_sm_init(struct idm_conn_s *ic); + +void +idm_conn_sm_fini(struct idm_conn_s *ic); + +idm_status_t +idm_notify_client(struct idm_conn_s *ic, idm_client_notify_t cn, + uintptr_t data); + +void +idm_conn_event(struct idm_conn_s *ic, idm_conn_event_t event, uintptr_t data); + +idm_status_t +idm_conn_reinstate_event(struct idm_conn_s *old_ic, struct idm_conn_s *new_ic); + +void +idm_conn_tx_pdu_event(struct idm_conn_s *ic, idm_conn_event_t event, + uintptr_t data); + +void +idm_conn_rx_pdu_event(struct idm_conn_s *ic, idm_conn_event_t event, + uintptr_t data); + +char * +idm_conn_state_str(struct idm_conn_s *ic); + +#ifdef __cplusplus +} +#endif + +#endif /* _IDM_CONN_SM_H_ */ diff --git a/usr/src/uts/common/sys/idm/idm_impl.h b/usr/src/uts/common/sys/idm/idm_impl.h new file mode 100644 index 000000000000..e18472a18d24 --- /dev/null +++ b/usr/src/uts/common/sys/idm/idm_impl.h @@ -0,0 +1,479 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#ifndef _IDM_IMPL_H_ +#define _IDM_IMPL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/* + * IDM lock order: + * + * idm_taskid_table_lock, idm_task_t.idt_mutex + */ + +#define CF_LOGIN_READY 0x00000001 +#define CF_INITIAL_LOGIN 0x00000002 +#define CF_ERROR 0x80000000 + +typedef enum { + CONN_TYPE_INI = 1, + CONN_TYPE_TGT +} idm_conn_type_t; + +/* + * Watchdog interval in seconds + */ +#define IDM_WD_INTERVAL 5 + +/* + * Timeout period before a TRANSPORT_FAIL event is generated in seconds + * if the connection is idle. + */ +#define IDM_TRANSPORT_FAIL_IDLE_TIMEOUT 30 + +/* + * IDM reference count structure. Audit code is shamelessly adapted + * from CIFS server. + */ + +#define REFCNT_AUDIT_STACK_DEPTH 16 +#define REFCNT_AUDIT_BUF_MAX_REC 16 + +typedef struct { + uint32_t anr_refcnt; + int anr_depth; + pc_t anr_stack[REFCNT_AUDIT_STACK_DEPTH]; +} refcnt_audit_record_t; + +typedef struct { + int anb_index; + int anb_max_index; + refcnt_audit_record_t anb_records[REFCNT_AUDIT_BUF_MAX_REC]; +} refcnt_audit_buf_t; + +#define REFCNT_AUDIT(_rf_) { \ + refcnt_audit_record_t *anr; \ + \ + anr = (_rf_)->ir_audit_buf.anb_records; \ + anr += (_rf_)->ir_audit_buf.anb_index; \ + (_rf_)->ir_audit_buf.anb_index++; \ + (_rf_)->ir_audit_buf.anb_index &= \ + (_rf_)->ir_audit_buf.anb_max_index; \ + anr->anr_refcnt = (_rf_)->ir_refcnt; \ + anr->anr_depth = getpcstack(anr->anr_stack, \ + REFCNT_AUDIT_STACK_DEPTH); \ +} + +struct idm_refcnt_s; + +typedef void (idm_refcnt_cb_t)(void *ref_obj); + +typedef enum { + REF_NOWAIT, + REF_WAIT_SYNC, + REF_WAIT_ASYNC +} idm_refcnt_wait_t; + +typedef struct idm_refcnt_s { + int ir_refcnt; + void *ir_referenced_obj; + idm_refcnt_wait_t ir_waiting; + kmutex_t ir_mutex; + kcondvar_t ir_cv; + idm_refcnt_cb_t *ir_cb; + refcnt_audit_buf_t ir_audit_buf; +} idm_refcnt_t; + +/* + * connection parameters - These parameters would be populated at + * connection create, or during key-value negotiation at login + */ +typedef struct idm_conn_params_s { + uint32_t max_dataseglen; +} idm_conn_param_t; + +typedef struct idm_svc_s { + list_node_t is_list_node; + kmutex_t is_mutex; + kcondvar_t is_cv; + kmutex_t is_count_mutex; + kcondvar_t is_count_cv; + idm_refcnt_t is_refcnt; + int is_online; + /* transport-specific service components */ + void *is_so_svc; + void *is_iser_svc; + idm_svc_req_t is_svc_req; +} idm_svc_t; + +typedef struct idm_conn_s { + list_node_t ic_list_node; + void *ic_handle; + idm_refcnt_t ic_refcnt; + idm_svc_t *ic_svc_binding; /* Target conn. only */ + idm_sockaddr_t ic_ini_dst_addr; + struct sockaddr_storage ic_laddr; /* conn local address */ + struct sockaddr_storage ic_raddr; /* conn remote address */ + idm_conn_state_t ic_state; + idm_conn_state_t ic_last_state; + sm_audit_buf_t ic_state_audit; + kmutex_t ic_state_mutex; + kcondvar_t ic_state_cv; + uint32_t ic_state_flags; + timeout_id_t ic_state_timeout; + struct idm_conn_s *ic_reinstate_conn; /* For conn reinst. */ + struct idm_conn_s *ic_logout_conn; /* For other conn logout */ + taskq_t *ic_state_taskq; + int ic_pdu_events; + boolean_t ic_login_info_valid; + boolean_t ic_rdma_extensions; + uint16_t ic_login_cid; + + kmutex_t ic_mutex; + kcondvar_t ic_cv; + idm_status_t ic_conn_sm_status; + + boolean_t ic_ffp; + uint32_t ic_internal_cid; + + uint32_t ic_conn_flags; + idm_conn_type_t ic_conn_type; + idm_conn_ops_t ic_conn_ops; + idm_transport_ops_t *ic_transport_ops; + idm_transport_type_t ic_transport_type; + int ic_transport_hdrlen; + void *ic_transport_private; + idm_conn_param_t ic_conn_params; + /* + * Save client callback to interpose idm callback + */ + idm_pdu_cb_t *ic_client_callback; + clock_t ic_timestamp; +} idm_conn_t; + +#define IDM_CONN_HEADER_DIGEST 0x00000001 +#define IDM_CONN_DATA_DIGEST 0x00000002 +#define IDM_CONN_USE_SCOREBOARD 0x00000004 + +#define IDM_CONN_ISINI(ICI_IC) ((ICI_IC)->ic_conn_type == CONN_TYPE_INI) +#define IDM_CONN_ISTGT(ICI_IC) ((ICI_IC)->ic_conn_type == CONN_TYPE_TGT) + +/* + * An IDM target task can transfer data using multiple buffers. The task + * will maintain a list of buffers, and each buffer will contain the relative + * offset of the transfer and a pointer to the next buffer in the list. + * + * Note on client private data: + * idt_private is intended to be a pointer to some sort of client- + * specific state. + * + * idt_client_handle is a more generic client-private piece of data that can + * be used by the client for the express purpose of task lookup. The driving + * use case for this is for the client to store the initiator task tag for + * a given task so that it may be more easily retrieved for task management. + * + * The key take away here is that clients should never call + * idm_task_find_by_handle in the performance path. + * + * An initiator will require only one buffer per task, the offset will be 0. + */ + +typedef struct idm_task_s { + idm_conn_t *idt_ic; /* Associated connection */ + /* connection type is in idt_ic->ic_conn_type */ + kmutex_t idt_mutex; + void *idt_private; /* Client private data */ + uintptr_t idt_client_handle; /* Client private */ + uint32_t idt_tt; /* Task tag */ + uint32_t idt_r2t_ttt; /* R2T Target Task tag */ + idm_task_state_t idt_state; + idm_refcnt_t idt_refcnt; + + /* + * Statistics + */ + int idt_tx_to_ini_start; + int idt_tx_to_ini_done; + int idt_rx_from_ini_start; + int idt_rx_from_ini_done; + + uint32_t idt_exp_datasn; /* expected datasn */ + uint32_t idt_exp_rttsn; /* expected rttsn */ + list_t idt_inbufv; /* chunks of IN buffers */ + list_t idt_outbufv; /* chunks of OUT buffers */ + + /* + * Transport header, which describes this tasks remote tagged buffer + */ + int idt_transport_hdrlen; + void *idt_transport_hdr; +} idm_task_t; + +int idm_task_constructor(void *task_void, void *arg, int flags); +void idm_task_destructor(void *task_void, void *arg); + +#define IDM_TASKIDS_MAX 16384 +#define IDM_BUF_MAGIC 0x49425546 /* "IBUF" */ + +/* Protect with task mutex */ +typedef struct idm_buf_s { + uint32_t idb_magic; /* "IBUF" */ + + /* + * Note: idm_tx_link *must* be the second element in the list for + * proper TX PDU ordering. + */ + list_node_t idm_tx_link; /* link in a list of TX objects */ + + list_node_t idb_buflink; /* link in a multi-buffer data xfer */ + idm_conn_t *idb_ic; /* Associated connection */ + void *idb_buf; /* data */ + uint64_t idb_buflen; /* length of buffer */ + size_t idb_bufoffset; /* offset in a multi-buffer xfer */ + boolean_t idb_bufalloc; /* true if alloc'd in idm_buf_alloc */ + /* + * DataPDUInOrder=Yes, so to track that the PDUs in a sequence are sent + * in continuously increasing address order, check that offsets for a + * single buffer xfer are in order. + */ + uint32_t idb_exp_offset; + size_t idb_xfer_len; /* Current requested xfer len */ + void *idb_buf_private; /* transport-specific buf handle */ + void *idb_reg_private; /* transport-specific reg handle */ + idm_buf_cb_t *idb_buf_cb; /* Data Completion Notify, tgt only */ + void *idb_cb_arg; /* Client private data */ + idm_task_t *idb_task_binding; + boolean_t idb_in_transport; + boolean_t idb_tx_thread; /* Sockets only */ + iscsi_hdr_t idb_data_hdr_tmpl; /* Sockets only */ + idm_status_t idb_status; +} idm_buf_t; + +#define PDU_MAX_IOVLEN 12 +#define IDM_PDU_MAGIC 0x49504455 /* "IPDU" */ + +typedef struct idm_pdu_s { + uint32_t isp_magic; /* "IPDU" */ + + /* + * Internal - Order is vital. idm_tx_link *must* be the second + * element in this structure for proper TX PDU ordering. + */ + list_node_t idm_tx_link; + + list_node_t isp_client_lnd; + + idm_conn_t *isp_ic; /* Must be set */ + iscsi_hdr_t *isp_hdr; + uint_t isp_hdrlen; + uint8_t *isp_data; + uint_t isp_datalen; + + /* Transport header */ + void *isp_transport_hdr; + uint32_t isp_transport_hdrlen; + void *isp_transport_private; + + /* + * isp_data is used for sending SCSI status, NOP, text, scsi and + * non-scsi data. Data is received using isp_iov and isp_iovlen + * to support data over multiple buffers. + */ + void *isp_private; + idm_pdu_cb_t *isp_callback; + idm_status_t isp_status; + + /* + * The following four elements are only used in + * idm_sorecv_scsidata() currently. + */ + struct iovec isp_iov[PDU_MAX_IOVLEN]; + int isp_iovlen; + idm_buf_t *isp_sorx_buf; + + /* Implementation data for idm_pdu_alloc and sorx PDU cache */ + uint32_t isp_flags; + uint_t isp_hdrbuflen; + uint_t isp_databuflen; +} idm_pdu_t; + +/* + * This "generic" object is used when removing an item from the ic_tx_list + * in order to determine whether it's an idm_pdu_t or an idm_buf_t + */ + +typedef struct { + uint32_t idm_tx_obj_magic; + /* + * idm_tx_link *must* be the second element in this structure. + */ + list_node_t idm_tx_link; +} idm_tx_obj_t; + + +#define IDM_PDU_OPCODE(PDU) \ + ((PDU)->isp_hdr->opcode & ISCSI_OPCODE_MASK) + +#define IDM_PDU_ALLOC 0x00000001 +#define IDM_PDU_ADDL_HDR 0x00000002 +#define IDM_PDU_ADDL_DATA 0x00000004 +#define IDM_PDU_LOGIN_TX 0x00000008 + +#define OSD_EXT_CDB_AHSLEN (200 - 15) +#define BIDI_AHS_LENGTH 5 +#define IDM_SORX_CACHE_AHSLEN \ + (((OSD_EXT_CDB_AHSLEN + 3) + \ + (BIDI_AHS_LENGTH + 3)) / sizeof (uint32_t)) +#define IDM_SORX_CACHE_HDRLEN (sizeof (iscsi_hdr_t) + IDM_SORX_CACHE_AHSLEN) + +/* + * ID pool + */ + +#define IDM_IDPOOL_MAGIC 0x4944504C /* IDPL */ +#define IDM_IDPOOL_MIN_SIZE 64 /* Number of IDs to begin with */ +#define IDM_IDPOOL_MAX_SIZE 64 * 1024 + +typedef struct idm_idpool { + uint32_t id_magic; + kmutex_t id_mutex; + uint8_t *id_pool; + uint32_t id_size; + uint8_t id_bit; + uint8_t id_bit_idx; + uint32_t id_idx; + uint32_t id_idx_msk; + uint32_t id_free_counter; + uint32_t id_max_free_counter; +} idm_idpool_t; + +/* + * Global IDM state structure + */ +typedef struct { + kmutex_t idm_global_mutex; + taskq_t *idm_global_taskq; + kthread_t *idm_wd_thread; + kt_did_t idm_wd_thread_did; + boolean_t idm_wd_thread_running; + kcondvar_t idm_wd_cv; + list_t idm_tgt_svc_list; + kcondvar_t idm_tgt_svc_cv; + list_t idm_tgt_conn_list; + int idm_tgt_conn_count; + list_t idm_ini_conn_list; + kmem_cache_t *idm_buf_cache; + kmem_cache_t *idm_task_cache; + krwlock_t idm_taskid_table_lock; + idm_task_t **idm_taskid_table; + uint32_t idm_taskid_next; + uint32_t idm_taskid_max; + idm_idpool_t idm_conn_id_pool; + kmem_cache_t *idm_sotx_pdu_cache; + kmem_cache_t *idm_sorx_pdu_cache; +} idm_global_t; + +idm_global_t idm; /* Global state */ + +int +idm_idpool_create(idm_idpool_t *pool); + +void +idm_idpool_destroy(idm_idpool_t *pool); + +int +idm_idpool_alloc(idm_idpool_t *pool, uint16_t *id); + +void +idm_idpool_free(idm_idpool_t *pool, uint16_t id); + +void +idm_pdu_rx(idm_conn_t *ic, idm_pdu_t *pdu); + +void +idm_pdu_tx_forward(idm_conn_t *ic, idm_pdu_t *pdu); + +boolean_t +idm_pdu_rx_forward_ffp(idm_conn_t *ic, idm_pdu_t *pdu); + +void +idm_pdu_rx_forward(idm_conn_t *ic, idm_pdu_t *pdu); + +void +idm_pdu_tx_protocol_error(idm_conn_t *ic, idm_pdu_t *pdu); + +void +idm_pdu_rx_protocol_error(idm_conn_t *ic, idm_pdu_t *pdu); + +void idm_parse_login_rsp(idm_conn_t *ic, idm_pdu_t *logout_req_pdu, + boolean_t rx); + +void idm_parse_logout_req(idm_conn_t *ic, idm_pdu_t *logout_req_pdu, + boolean_t rx); + +void idm_parse_logout_rsp(idm_conn_t *ic, idm_pdu_t *login_rsp_pdu, + boolean_t rx); + +idm_status_t idm_svc_conn_create(idm_svc_t *is, idm_transport_type_t type, + idm_conn_t **ic_result); + +void idm_svc_conn_destroy(idm_conn_t *ic); + +idm_status_t idm_ini_conn_finish(idm_conn_t *ic); + +idm_status_t idm_tgt_conn_finish(idm_conn_t *ic); + +idm_conn_t *idm_conn_create_common(idm_conn_type_t conn_type, + idm_transport_type_t tt, idm_conn_ops_t *conn_ops); + +void idm_conn_destroy_common(idm_conn_t *ic); + +void idm_conn_close(idm_conn_t *ic); + +uint32_t idm_cid_alloc(void); + +void idm_cid_free(uint32_t cid); + +uint32_t idm_crc32c(void *address, unsigned long length); + +uint32_t idm_crc32c_continued(void *address, unsigned long length, + uint32_t crc); + +void idm_listbuf_insert(list_t *lst, idm_buf_t *buf); + +int idm_task_compare(const void *v1, const void *v2); + +idm_conn_t *idm_lookup_conn(uint8_t *isid, uint16_t tsih, uint16_t cid); + +#ifdef __cplusplus +} +#endif + +#endif /* _IDM_IMPL_H_ */ diff --git a/usr/src/uts/common/sys/idm/idm_so.h b/usr/src/uts/common/sys/idm/idm_so.h new file mode 100644 index 000000000000..134896ed4fca --- /dev/null +++ b/usr/src/uts/common/sys/idm/idm_so.h @@ -0,0 +1,110 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _IDM_SO_H +#define _IDM_SO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* + * Define TCP window size (send and receive buffer sizes) + */ + +#define IDM_RCVBUF_SIZE (256 * 1024) +#define IDM_SNDBUF_SIZE (256 * 1024) + +/* sockets-specific portion of idm_svc_t */ +typedef struct idm_so_svc_s { + struct sonode *is_so; + kthread_t *is_thread; + kt_did_t is_thread_did; + boolean_t is_thread_running; +} idm_so_svc_t; + +/* sockets-specific portion of idm_conn_t */ +typedef struct idm_so_conn_s { + struct sonode *ic_so; + + kthread_t *ic_tx_thread; + kt_did_t ic_tx_thread_did; + boolean_t ic_tx_thread_running; + kmutex_t ic_tx_mutex; + kcondvar_t ic_tx_cv; + list_t ic_tx_list; /* List of PDUs for transmit */ + + kthread_t *ic_rx_thread; + kt_did_t ic_rx_thread_did; + boolean_t ic_rx_thread_running; +} idm_so_conn_t; + +void idm_so_init(idm_transport_t *it); +void idm_so_fini(); + +/* Socket functions */ + +struct sonode * +idm_socreate(int domain, int type, int protocol); + +void idm_soshutdown(struct sonode *so); + +void idm_sodestroy(struct sonode *so); + +int idm_get_ipaddr(idm_addr_list_t **); + +int idm_sorecv(struct sonode *so, void *msg, size_t len); + +int idm_sosendto(struct sonode *so, void *buff, size_t len, + struct sockaddr *name, socklen_t namelen); + +int idm_iov_sosend(struct sonode *so, iovec_t *iop, int iovlen, + size_t total_len); + +int idm_iov_sorecv(struct sonode *so, iovec_t *iop, int iovlen, + size_t total_len); + +void idm_sotx_thread(void *arg); +void idm_sorx_thread(void *arg); + + +int idm_sotx_pdu_constructor(void *hdl, void *arg, int flags); + +void idm_sotx_pdu_destructor(void *pdu_void, void *arg); + +int idm_sorx_pdu_constructor(void *hdl, void *arg, int flags); + +void idm_sorx_pdu_destructor(void *pdu_void, void *arg); + +void idm_so_svc_port_watcher(void *arg); + + +#ifdef __cplusplus +} +#endif + +#endif /* _IDM_SO_H */ diff --git a/usr/src/uts/common/sys/idm/idm_text.h b/usr/src/uts/common/sys/idm/idm_text.h new file mode 100644 index 000000000000..35649466a7c8 --- /dev/null +++ b/usr/src/uts/common/sys/idm/idm_text.h @@ -0,0 +1,198 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#ifndef _IDM_TEXT_H_ +#define _IDM_TEXT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* + * Numerical identifiers for iSCSI name-value pair keys (just so that + * we can use case statements to handle a particular key-value pair + * after we find it in idm_kvpair_xlate). + * + * We want to use a bitmask to keep track of negotiated key-value pairs + * so keep this enum under 64 values -- or spend some time reworking the + * login code. + */ +typedef enum { + KI_AUTH_METHOD = 1, + KI_KRB_AP_REQ, + KI_KRB_AP_REP, + + /* SPKM */ + KI_SPKM_REQ, + KI_SPKM_ERROR, + KI_SPKM_REP_TI, + KI_SPKM_REP_IT, + + /* + * SRP + */ + KI_SRP_U, + KI_TARGET_AUTH, + KI_SRP_GROUP, + KI_SRP_A, + KI_SRP_B, + KI_SRP_M, + KI_SRM_HM, + + /* + * CHAP + */ + KI_CHAP_A, + KI_CHAP_I, + KI_CHAP_C, + KI_CHAP_N, + KI_CHAP_R, + + + /* + * ISCSI Operational Parameter Keys + */ + KI_HEADER_DIGEST, + KI_DATA_DIGEST, + KI_MAX_CONNECTIONS, + KI_SEND_TARGETS, + KI_TARGET_NAME, + KI_INITIATOR_NAME, + KI_TARGET_ALIAS, + KI_INITIATOR_ALIAS, + KI_TARGET_ADDRESS, + KI_TARGET_PORTAL_GROUP_TAG, + KI_INITIAL_R2T, + KI_IMMEDIATE_DATA, + KI_MAX_RECV_DATA_SEGMENT_LENGTH, + KI_MAX_BURST_LENGTH, + KI_FIRST_BURST_LENGTH, + KI_DEFAULT_TIME_2_WAIT, + KI_DEFAULT_TIME_2_RETAIN, + KI_MAX_OUTSTANDING_R2T, + KI_DATA_PDU_IN_ORDER, + KI_DATA_SEQUENCE_IN_ORDER, + KI_ERROR_RECOVERY_LEVEL, + KI_SESSION_TYPE, + KI_OFMARKER, + KI_OFMARKERINT, + KI_IFMARKER, + KI_IFMARKERINT, + + /* + * iSER-specific keys + */ + KI_RDMA_EXTENSIONS, + KI_TARGET_RECV_DATA_SEGMENT_LENGTH, + KI_INITIATOR_RECV_DATA_SEGMENT_LENGTH, + KI_MAX_OUTSTANDING_UNEXPECTED_PDUS, + + /* + * End of list marker, no keys below here. + */ + KI_MAX_KEY +} iscsikey_id_t; + +/* Numerical types for iSCSI name-value pair values */ +typedef enum { + KT_TEXT, + KT_ISCSI_NAME, + KT_ISCSI_LOCAL_NAME, + KT_BOOLEAN, + KT_NUMERICAL, /* Hex or decimal constant */ + KT_LARGE_NUMERICAL, /* Hex, decimal or Base64 constant */ + KT_NUMERIC_RANGE, + KT_REGULAR_BINARY, /* Hex, decimal, base64 not longer than 64 bits */ + KT_LARGE_BINARY, /* Hex, decimal, base64 longer than 64 bites */ + KT_BINARY, /* Regular binary or large binary */ + KT_SIMPLE, + KT_LIST_OF_VALUES +} idmkey_type_t; + +typedef struct { + iscsikey_id_t ik_key_id; + char *ik_key_name; + idmkey_type_t ik_idm_type; /* RFC type */ + boolean_t ik_declarative; +} idm_kv_xlate_t; + +const idm_kv_xlate_t * +idm_lookup_kv_xlate(const char *key, int keylen); + +int +idm_nvlist_add_keyvalue(nvlist_t *nvl, char *key, int keylen, char *value); + +int +idm_textbuf_to_nvlist(nvlist_t *nvl, char **textbuf, int *textbuflen); + +int +idm_textbuf_to_firstfraglen(void *textbuf, int textbuflen); + +int +idm_nvlist_to_textbuf(nvlist_t *nvl, char **textbuf, int *textbuflen, + int *tblen_required); + +kv_status_t +idm_nvstat_to_kvstat(int nvrc); + +void +idm_kvstat_to_error(kv_status_t kvrc, uint8_t *class, uint8_t *detail); + +int +idm_nvlist_add_id(nvlist_t *nvl, iscsikey_id_t kv_id, char *value); + +nvpair_t * +idm_get_next_listvalue(nvpair_t *value_list, nvpair_t *curr_nvp); + +char * +idm_id_to_name(iscsikey_id_t kv_id); + +char * +idm_nvpair_value_to_textbuf(nvpair_t *nvp); + +idm_status_t +idm_pdu_list_to_nvlist(list_t *pdu_list, nvlist_t **nvlist, + uint8_t *error_detail); + +void * +idm_nvlist_to_itextbuf(nvlist_t *nvl); + +char * +idm_pdu_init_text_data(idm_pdu_t *pdu, void *arg, + int max_xfer_len, char *bufptr, int *transit); + +void +idm_itextbuf_free(void *arg); + +int +idm_strtoull(const char *str, char **nptr, int base, + unsigned long long *result); + +#ifdef __cplusplus +} +#endif + +#endif /* _IDM_TEXT_H_ */ diff --git a/usr/src/uts/common/sys/idm/idm_transport.h b/usr/src/uts/common/sys/idm/idm_transport.h new file mode 100644 index 000000000000..0f763f9636b5 --- /dev/null +++ b/usr/src/uts/common/sys/idm/idm_transport.h @@ -0,0 +1,221 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _IDM_TRANSPORT_H_ +#define _IDM_TRANSPORT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define IDM_TRANSPORT_PATHLEN 0x40 + +/* Note, this is tied to iSER currently */ +#define IDM_TRANSPORT_HEADER_LENGTH 0x20 + +/* + * idm_transport_type_t + * An enumerated list of the transports available to iSER. + * Note that new transports should be added to the enum prior to NUM_TYPES. + */ +typedef enum { + IDM_TRANSPORT_TYPE_ISER = 0, + IDM_TRANSPORT_TYPE_SOCKETS, + IDM_TRANSPORT_NUM_TYPES, + IDM_TRANSPORT_TYPE_UNDEFINED +} idm_transport_type_t; + +/* + * idm_transport_caps_t + * Encodes a set of attributes describing an IDM transport's capabilities. + * JB - do we need this? + */ +typedef struct idm_transport_caps_s { + uint32_t flags; +} idm_transport_caps_t; + +/* + * Transport routine definitions for idm_transport_ops_t below + */ + +/* Send_Control - transmit a Control-type PDU */ +typedef void (transport_tx_op_t)(struct idm_conn_s *ic, struct idm_pdu_s *pdu); + +/* + * Target transport data primitives, caller (IDM) holds idt->idt_mutex, + * and the transport should release the mutex before returning. + */ +typedef idm_status_t (transport_buf_tx_to_ini_op_t)(struct idm_task_s *idt, + struct idm_buf_s *idb); +typedef idm_status_t (transport_buf_rx_from_ini_op_t)(struct idm_task_s *idt, + struct idm_buf_s *idb); + +/* Initiator transport data handlers */ +typedef void (transport_rx_datain_op_t)(struct idm_conn_s *ic, + struct idm_pdu_s *pdu); +typedef void (transport_rx_rtt_op_t)(struct idm_conn_s *ic, + struct idm_pdu_s *pdu); + +/* Target transport Data-out handler */ +typedef void (transport_rx_dataout_op_t)(struct idm_conn_s *ic, + struct idm_pdu_s *pdu); + +/* Transport-specific resource allocation and free */ +typedef idm_status_t (transport_alloc_conn_rsrc_op_t)(struct idm_conn_s *ic); +typedef idm_status_t (transport_free_conn_rsrc_op_t)(struct idm_conn_s *ic); + +/* Transport driver operations enable/disable */ +typedef idm_status_t (transport_tgt_enable_datamover_op_t)(struct + idm_conn_s *ic); +typedef idm_status_t (transport_ini_enable_datamover_op_t)(struct + idm_conn_s *ic); +typedef idm_status_t (transport_conn_terminate_op_t)(struct idm_conn_s *ic); + +/* Task resource cleanup */ +typedef idm_status_t (transport_free_task_rsrcs_op_t)(struct idm_task_s *it); + +/* Negotiate key value pairs */ +typedef kv_status_t (transport_negotiate_key_values_op_t)(struct + idm_conn_s *ic, nvlist_t *request_nvl, nvlist_t *response_nvl, + nvlist_t *negotiated_nvl); + +/* Activate the negotiated key value pairs */ +typedef idm_status_t (transport_notice_key_values_op_t)(struct idm_conn_s *ic, + nvlist_t *negotiated_nvl); + +/* Transport capability probe */ +typedef boolean_t (transport_conn_is_capable_op_t)(idm_conn_req_t *ic, + struct idm_transport_caps_s *caps); + +/* Transport buffer services */ +typedef idm_status_t (transport_buf_alloc_op_t)(struct idm_buf_s *idb, + uint64_t buflen); +typedef idm_status_t (transport_buf_setup_op_t)(struct idm_buf_s *idb); +typedef void (transport_buf_teardown_op_t)(struct idm_buf_s *idb); +typedef void (transport_buf_free_op_t)(struct idm_buf_s *idb); + +/* Transport target context and service management services */ +typedef idm_status_t (transport_tgt_svc_create_op_t)(idm_svc_req_t *sr, + struct idm_svc_s *is); +typedef void (transport_tgt_svc_destroy_op_t)(struct idm_svc_s *is); +typedef idm_status_t (transport_tgt_svc_online_op_t)(struct idm_svc_s *is); +typedef void (transport_tgt_svc_offline_op_t)(struct idm_svc_s *is); + +/* Transport target connection establishment */ +typedef void (transport_tgt_conn_destroy_op_t)(struct idm_conn_s *ic); +typedef idm_status_t (transport_tgt_conn_connect_op_t)(struct idm_conn_s *ic); +typedef void (transport_tgt_conn_disconnect_op_t)(struct idm_conn_s *ic); + +/* Transport initiator context and connection management services */ +typedef idm_status_t (transport_ini_conn_create_op_t)(idm_conn_req_t *cr, + struct idm_conn_s *ic); +typedef void (transport_ini_conn_destroy_op_t)(struct idm_conn_s *ic); +typedef idm_status_t (transport_ini_conn_connect_op_t)(struct idm_conn_s *ic); +typedef void (transport_ini_conn_disconnect_op_t)(struct idm_conn_s *ic); + + +/* + * idm_transport_ops_t + * Encodes a set of vectors into an IDM transport driver that implement the + * transport-specific Datamover operations for IDM usage. These routines are + * invoked by the IDM layer to execute the transport-specific implementations + * of the DataMover primitives and supporting routines. + */ +typedef struct idm_transport_ops_s { + transport_tx_op_t *it_tx_pdu; + transport_buf_tx_to_ini_op_t *it_buf_tx_to_ini; + transport_buf_rx_from_ini_op_t *it_buf_rx_from_ini; + transport_rx_datain_op_t *it_rx_datain; + transport_rx_rtt_op_t *it_rx_rtt; + transport_rx_dataout_op_t *it_rx_dataout; + transport_alloc_conn_rsrc_op_t *it_alloc_conn_rsrc; + transport_free_conn_rsrc_op_t *it_free_conn_rsrc; + transport_tgt_enable_datamover_op_t *it_tgt_enable_datamover; + transport_ini_enable_datamover_op_t *it_ini_enable_datamover; + transport_conn_terminate_op_t *it_conn_terminate; + transport_free_task_rsrcs_op_t *it_free_task_rsrc; + transport_negotiate_key_values_op_t *it_negotiate_key_values; + transport_notice_key_values_op_t *it_notice_key_values; + transport_conn_is_capable_op_t *it_conn_is_capable; + transport_buf_alloc_op_t *it_buf_alloc; + transport_buf_free_op_t *it_buf_free; + transport_buf_setup_op_t *it_buf_setup; + transport_buf_teardown_op_t *it_buf_teardown; + transport_tgt_svc_create_op_t *it_tgt_svc_create; + transport_tgt_svc_destroy_op_t *it_tgt_svc_destroy; + transport_tgt_svc_online_op_t *it_tgt_svc_online; + transport_tgt_svc_offline_op_t *it_tgt_svc_offline; + transport_tgt_conn_destroy_op_t *it_tgt_conn_destroy; + transport_tgt_conn_connect_op_t *it_tgt_conn_connect; + transport_tgt_conn_disconnect_op_t *it_tgt_conn_disconnect; + transport_ini_conn_create_op_t *it_ini_conn_create; + transport_ini_conn_destroy_op_t *it_ini_conn_destroy; + transport_ini_conn_connect_op_t *it_ini_conn_connect; + transport_ini_conn_disconnect_op_t *it_ini_conn_disconnect; +} idm_transport_ops_t; + +/* + * idm_transport_t encodes all of the data related to an IDM transport + * type. In addition to type and capabilities, it also stores a pointer + * to the connection and transport operation implementations, and also + * it stores the LDI handle. + */ +typedef struct idm_transport_s { + idm_transport_type_t it_type; + char *it_device_path; + ldi_handle_t it_ldi_hdl; + idm_transport_ops_t *it_ops; + idm_transport_caps_t *it_caps; +} idm_transport_t; + +/* + * idm_transport_attr_t encodes details of a transport driver seeking + * registration with the IDM kernel module. + */ +typedef struct idm_transport_attr_s { + idm_transport_type_t type; + idm_transport_ops_t *it_ops; + idm_transport_caps_t *it_caps; +} idm_transport_attr_t; + +/* IDM transport API */ +idm_status_t +idm_transport_register(idm_transport_attr_t *attr); + +idm_transport_t * +idm_transport_lookup(idm_conn_req_t *cr); + +void +idm_transport_setup(ldi_ident_t li); + +#ifdef __cplusplus +} +#endif + +#endif /* _IDM_TRANSPORT_H_ */ diff --git a/usr/src/uts/common/sys/iscsi_protocol.h b/usr/src/uts/common/sys/iscsi_protocol.h index 1f529c4d8405..0da073029049 100644 --- a/usr/src/uts/common/sys/iscsi_protocol.h +++ b/usr/src/uts/common/sys/iscsi_protocol.h @@ -19,15 +19,13 @@ * CDDL HEADER END */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _ISCSI_PROTOCOL_H #define _ISCSI_PROTOCOL_H -#pragma ident "%Z%%M% %I% %E% SMI" - #ifdef __cplusplus extern "C" { #endif @@ -85,6 +83,12 @@ uint32_t iscsi_crc32c_continued(void *address, unsigned long length, /* text separtor between key value pairs exhanged in login */ #define ISCSI_TEXT_SEPARATOR '=' +/* reserved text constants for Text Mode Negotiation */ +#define ISCSI_TEXT_NONE "None" +#define ISCSI_TEXT_REJECT "Reject" +#define ISCSI_TEXT_IRRELEVANT "Irrelevant" +#define ISCSI_TEXT_NOTUNDERSTOOD "NotUnderstood" + /* Sun's initiator session ID */ #define ISCSI_SUN_ISID_0 0x40 /* ISID - EN format */ #define ISCSI_SUN_ISID_1 0x00 /* Sec B */ @@ -357,10 +361,18 @@ typedef struct _iscsi_scsi_task_mgt_rsp_hdr { #define SCSI_TCP_TM_RESP_NO_TASK 0x01 #define SCSI_TCP_TM_RESP_NO_LUN 0x02 #define SCSI_TCP_TM_RESP_TASK_ALLEGIANT 0x03 -#define SCSI_TCP_TM_RESP_NO_FAILOVER 0x04 -#define SCSI_TCP_TM_RESP_IN_PRGRESS 0x05 +#define SCSI_TCP_TM_RESP_NO_ALLG_REASSN 0x04 +#define SCSI_TCP_TM_RESP_FUNC_NOT_SUPP 0x05 +#define SCSI_TCP_TM_RESP_FUNC_AUTH_FAIL 0x06 #define SCSI_TCP_TM_RESP_REJECTED 0xff +/* + * Maintained for backward compatibility. + */ + +#define SCSI_TCP_TM_RESP_NO_FAILOVER SCSI_TCP_TM_RESP_NO_ALLG_REASSN +#define SCSI_TCP_TM_RESP_IN_PRGRESS SCSI_TCP_TM_RESP_FUNC_NOT_SUPP + /* Ready To Transfer Header */ typedef struct _iscsi_rtt_hdr { uint8_t opcode; @@ -464,6 +476,8 @@ typedef struct _iscsi_text_rsp_hdr { */ } iscsi_text_rsp_hdr_t; +#define ISCSI_ISID_LEN 6 + /* Login Header */ typedef struct _iscsi_login_hdr { uint8_t opcode; @@ -472,7 +486,7 @@ typedef struct _iscsi_login_hdr { uint8_t min_version; /* Min. version supported */ uint8_t hlength; uint8_t dlength[3]; - uint8_t isid[6]; /* Initiator Session ID */ + uint8_t isid[ISCSI_ISID_LEN]; /* Initiator Session ID */ uint16_t tsid; /* Target Session ID */ uint32_t itt; /* Initiator Task Tag */ uint16_t cid; @@ -502,7 +516,7 @@ typedef struct _iscsi_login_rsp_hdr { uint8_t active_version; /* Active version */ uint8_t hlength; uint8_t dlength[3]; - uint8_t isid[6]; /* Initiator Session ID */ + uint8_t isid[ISCSI_ISID_LEN]; /* Initiator Session ID */ uint16_t tsid; /* Target Session ID */ uint32_t itt; /* Initiator Task Tag */ uint32_t rsvd3; @@ -631,7 +645,9 @@ typedef struct _iscsi_reject_rsp_hdr { uint8_t rsvd2; uint8_t rsvd3; uint8_t dlength[3]; - uint8_t rsvd4[16]; + uint8_t rsvd4[8]; + uint8_t must_be_ff[4]; + uint8_t rsvd4a[4]; uint32_t statsn; uint32_t expcmdsn; uint32_t maxcmdsn; @@ -675,6 +691,19 @@ typedef struct _iscsi_reject_rsp_hdr { #define ISCSI_DEFAULT_IFMARKER FALSE #define ISCSI_DEFAULT_OFMARKER FALSE +/* + * Minimum values from the iSCSI specification + */ + +#define ISCSI_MIN_TIME2RETAIN 0 +#define ISCSI_MIN_TIME2WAIT 0 +#define ISCSI_MIN_ERROR_RECOVERY_LEVEL 0 +#define ISCSI_MIN_RECV_DATA_SEGMENT_LENGTH 0x200 +#define ISCSI_MIN_FIRST_BURST_LENGTH 0x200 +#define ISCSI_MIN_MAX_BURST_LENGTH 0x200 +#define ISCSI_MIN_CONNECTIONS 1 +#define ISCSI_MIN_MAX_OUTSTANDING_R2T 1 + /* * Maximum values from the iSCSI specification */ diff --git a/usr/src/uts/common/sys/iscsit/chap.h b/usr/src/uts/common/sys/iscsit/chap.h new file mode 100644 index 000000000000..c1fe54005b7d --- /dev/null +++ b/usr/src/uts/common/sys/iscsit/chap.h @@ -0,0 +1,94 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _CHAP_H +#define _CHAP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include + +typedef enum chap_validation_status_type { + CHAP_VALIDATION_PASSED, /* CHAP validation passed */ + CHAP_VALIDATION_INVALID_RESPONSE, /* Invalid CHAP response */ + CHAP_VALIDATION_DUP_SECRET, /* Same CHAP secret used */ + /* for authentication in the */ + /* other direction */ + CHAP_VALIDATION_UNKNOWN_AUTH_METHOD, /* Unknown authentication */ + /* method */ + CHAP_VALIDATION_INTERNAL_ERROR, /* MISC internal error */ + CHAP_VALIDATION_RADIUS_ACCESS_ERROR, /* Problem accessing RADIUS */ + CHAP_VALIDATION_BAD_RADIUS_SECRET, /* Invalid RADIUS shared */ + /* secret */ + CHAP_VALIDATION_UNKNOWN_RADIUS_CODE /* Irrelevant or unknown */ + /* RADIUS packet code */ + /* returned */ +} chap_validation_status_type; + +typedef enum authentication_method_type { + RADIUS_AUTHENTICATION, + DIRECT_AUTHENTICATION +} authentication_method_type; + +typedef struct radius_config { + iscsi_ipaddr_t rad_svr_addr; /* IPv6 enabled */ + uint32_t rad_svr_port; + uint8_t rad_svr_shared_secret[MAX_RAD_SHARED_SECRET_LEN]; + uint32_t rad_svr_shared_secret_len; +} RADIUS_CONFIG; + +/* + * To validate a target CHAP response given the associated challenge. + * + * target_chap_name - The CHAP name of the target being authenticated. + * initiator_chap_name - The CHAP name of the authenticating initiator. + * challenge - The CHAP challenge to which the target responded. + * target_response - The target's CHAP response to be validated. + * identifier - The identifier associated with the CHAP challenge. + * auth_method - The authentication method to be used. + * auth_config_data - Any required configuration data to support the + * specified authentication method. + */ +chap_validation_status_type +chap_validate( + char *target_chap_name, + char *initiator_chap_name, + uint8_t *challenge, + uint8_t *target_response, + uint8_t identifier, + authentication_method_type auth_method, + void *auth_config_data); + +#ifdef __cplusplus +} +#endif + +#endif /* _CHAP_H */ diff --git a/usr/src/uts/common/sys/iscsit/iscsi_if.h b/usr/src/uts/common/sys/iscsit/iscsi_if.h new file mode 100644 index 000000000000..c29025463847 --- /dev/null +++ b/usr/src/uts/common/sys/iscsit/iscsi_if.h @@ -0,0 +1,651 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ISCSI_IF_H +#define _ISCSI_IF_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _KERNEL +#include +#include /* for prototype of kstrgetmsg */ +#include +#include /* for struct sonode */ +#endif +#include +#include +#include + +/* + * Each of the top level structures have a version field as + * the first member. That version value will be set by the + * caller. The consumer of the structure will check to see + * if the version is correct. + */ +#define ISCSI_INTERFACE_VERSION 3 + +/* + * The maximum length of an iSCSI name is 223. 224 is used + * to provide space for a null character. + */ +#define ISCSI_MAX_NAME_LEN 224 + +/* + * Login parameter values are used instead of ascii text + * between the IMA plug-in and kernel. + */ +#define ISCSI_LOGIN_PARAM_DATA_SEQUENCE_IN_ORDER 0x0000 /* bool */ +#define ISCSI_LOGIN_PARAM_IMMEDIATE_DATA 0x0001 /* bool */ +#define ISCSI_LOGIN_PARAM_INITIAL_R2T 0x0002 /* bool */ +#define ISCSI_LOGIN_PARAM_DATA_PDU_IN_ORDER 0x0003 /* bool */ +#define ISCSI_LOGIN_PARAM_HEADER_DIGEST 0x0004 /* int */ +#define ISCSI_LOGIN_PARAM_DATA_DIGEST 0x0005 /* int */ +#define ISCSI_LOGIN_PARAM_DEFAULT_TIME_2_RETAIN 0x0006 /* int */ +#define ISCSI_LOGIN_PARAM_DEFAULT_TIME_2_WAIT 0x0007 /* int */ +#define ISCSI_LOGIN_PARAM_MAX_RECV_DATA_SEGMENT_LENGTH 0x0008 /* int */ +#define ISCSI_LOGIN_PARAM_FIRST_BURST_LENGTH 0x0009 /* int */ +#define ISCSI_LOGIN_PARAM_MAX_BURST_LENGTH 0x000A /* int */ +#define ISCSI_LOGIN_PARAM_MAX_CONNECTIONS 0x000B /* int */ +#define ISCSI_LOGIN_PARAM_OUTSTANDING_R2T 0x000C /* int */ +#define ISCSI_LOGIN_PARAM_ERROR_RECOVERY_LEVEL 0x000D /* int */ +/* + * number of login parameters - needs to be updated when new parameter added + */ +#define ISCSI_NUM_LOGIN_PARAM 0x000E + +/* + * Used internally by the persistent store code. Currently a bitmap is kept of + * which params are currently set. This allows for quick a look up instead of + * cycling through the possible entries. Using an unsigned int as the bitmap we + * can have parameter numbers up through 31. Since the current only has 22 + * we're okay. + */ +#define ISCSI_LOGIN_PARAM_DB_ENTRY 0x0020 +/* + * Special case. When this parameter value is set in iscsi_param_set_t + * the member s_value (type iscsi_param_set_t) is not used. + * The name field contains the InitiatorName for the system which + * should be used for all future sessions. + */ +#define ISCSI_LOGIN_PARAM_INITIATOR_NAME 0x0021 +#define ISCSI_LOGIN_PARAM_INITIATOR_ALIAS 0x0022 + +#define ISCSI_DEVCTL "devctl" +#define ISCSI_DRIVER_DEVCTL "/devices/iscsi:" ISCSI_DEVCTL + +/* + * ioctls supported by the driver. + */ +#define ISCSI_IOCTL (('i' << 24) | ('S' << 16) | ('C' << 8)) +#define ISCSI_CREATE_OID (ISCSI_IOCTL | 2) +#define ISCSI_LOGIN (ISCSI_IOCTL | 3) +#define ISCSI_LOGOUT (ISCSI_IOCTL | 4) +#define ISCSI_PARAM_GET (ISCSI_IOCTL | 5) +#define ISCSI_PARAM_SET (ISCSI_IOCTL | 6) +#define ISCSI_TARGET_PARAM_CLEAR (ISCSI_IOCTL | 8) +#define ISCSI_TARGET_OID_LIST_GET (ISCSI_IOCTL | 9) +#define ISCSI_TARGET_PROPS_GET (ISCSI_IOCTL | 10) +#define ISCSI_TARGET_PROPS_SET (ISCSI_IOCTL | 11) +#define ISCSI_TARGET_ADDRESS_GET (ISCSI_IOCTL | 12) +#define ISCSI_CHAP_SET (ISCSI_IOCTL | 13) +#define ISCSI_CHAP_GET (ISCSI_IOCTL | 14) +#define ISCSI_CHAP_CLEAR (ISCSI_IOCTL | 15) +#define ISCSI_STATIC_GET (ISCSI_IOCTL | 16) +#define ISCSI_STATIC_SET (ISCSI_IOCTL | 17) +#define ISCSI_STATIC_CLEAR (ISCSI_IOCTL | 18) +#define ISCSI_DISCOVERY_SET (ISCSI_IOCTL | 19) +#define ISCSI_DISCOVERY_GET (ISCSI_IOCTL | 20) +#define ISCSI_DISCOVERY_CLEAR (ISCSI_IOCTL | 21) +#define ISCSI_DISCOVERY_PROPS (ISCSI_IOCTL | 22) +#define ISCSI_DISCOVERY_ADDR_SET (ISCSI_IOCTL | 23) +#define ISCSI_DISCOVERY_ADDR_LIST_GET (ISCSI_IOCTL | 24) +#define ISCSI_DISCOVERY_ADDR_CLEAR (ISCSI_IOCTL | 25) +#define ISCSI_RADIUS_SET (ISCSI_IOCTL | 26) +#define ISCSI_RADIUS_GET (ISCSI_IOCTL | 27) +#define ISCSI_DB_RELOAD (ISCSI_IOCTL | 28) +#define ISCSI_LUN_OID_LIST_GET (ISCSI_IOCTL | 29) +#define ISCSI_LUN_PROPS_GET (ISCSI_IOCTL | 30) +#define ISCSI_CONN_OID_LIST_GET (ISCSI_IOCTL | 31) +#define ISCSI_CONN_PROPS_GET (ISCSI_IOCTL | 32) +#define ISCSI_USCSI (ISCSI_IOCTL | 33) +#define ISCSI_DOOR_HANDLE_SET (ISCSI_IOCTL | 34) +#define ISCSI_DISCOVERY_EVENTS (ISCSI_IOCTL | 35) +#define ISCSI_AUTH_SET (ISCSI_IOCTL | 36) +#define ISCSI_AUTH_GET (ISCSI_IOCTL | 37) +#define ISCSI_AUTH_CLEAR (ISCSI_IOCTL | 38) +#define ISCSI_SENDTGTS_GET (ISCSI_IOCTL | 39) +#define ISCSI_ISNS_SERVER_ADDR_SET (ISCSI_IOCTL | 40) +#define ISCSI_ISNS_SERVER_ADDR_LIST_GET (ISCSI_IOCTL | 41) +#define ISCSI_ISNS_SERVER_ADDR_CLEAR (ISCSI_IOCTL | 42) +#define ISCSI_ISNS_SERVER_GET (ISCSI_IOCTL | 43) +#define ISCSI_GET_CONFIG_SESSIONS (ISCSI_IOCTL | 44) +#define ISCSI_SET_CONFIG_SESSIONS (ISCSI_IOCTL | 45) +#define ISCSI_INIT_NODE_NAME_SET (ISCSI_IOCTL | 46) +#define ISCSI_DB_DUMP (ISCSI_IOCTL | 100) /* DBG */ + +/* + * Misc. defines + */ +#define ISCSI_CHAP_NAME_LEN 512 +#define ISCSI_CHAP_SECRET_LEN 16 +#define ISCSI_TGT_OID_LIST 0x0001 +#define ISCSI_STATIC_TGT_OID_LIST 0x0002 +#define ISCSI_TGT_PARAM_OID_LIST 0x0004 +#define ISCSI_SESS_PARAM 0x0001 +#define ISCSI_CONN_PARAM 0x0002 + +/* digest level defines */ +#define ISCSI_DIGEST_NONE 0 +#define ISCSI_DIGEST_CRC32C 1 +#define ISCSI_DIGEST_CRC32C_NONE 2 /* offer both, prefer CRC32C */ +#define ISCSI_DIGEST_NONE_CRC32C 3 /* offer both, prefer None */ + +/* + * A last error associated with each target session is returned in the + * iscsi_target_t structure. + */ +typedef enum iscsi_error { + NoError, AuthenticationError, LoginParamError, ConnectionReset +} iscsi_error_t; + +/* + * The values associated with each enum is based on the IMA specification. + */ +typedef enum iSCSIDiscoveryMethod { + iSCSIDiscoveryMethodUnknown = 0, + iSCSIDiscoveryMethodStatic = 1, + iSCSIDiscoveryMethodSLP = 2, + iSCSIDiscoveryMethodISNS = 4, + iSCSIDiscoveryMethodSendTargets = 8 +} iSCSIDiscoveryMethod_t; +#define ISCSI_ALL_DISCOVERY_METHODS (iSCSIDiscoveryMethodStatic | \ + iSCSIDiscoveryMethodSLP | \ + iSCSIDiscoveryMethodISNS | \ + iSCSIDiscoveryMethodSendTargets) + +/* + * Before anything can be done to a target it must have an OID. + */ +typedef struct iscsi_oid { + uint32_t o_vers; /* In */ + uchar_t o_name[ISCSI_MAX_NAME_LEN]; /* In */ + /* + * tpgt is only 16 bits per spec. use 32 in ioctl to reduce + * packing issue. Also -1 tpgt denotes default value. iSCSI + * stack will detemermine tpgt during login. + */ + int o_tpgt; /* In */ + uint32_t o_oid; /* Out */ +} iscsi_oid_t; +#define ISCSI_OID_NOTSET 0 +#define ISCSI_INITIATOR_OID 1 /* Other OIDs follow > 1 */ +#define ISCSI_DEFAULT_TPGT -1 + +/* + * iSCSI Login Parameters - Reference iscsi draft for + * definitions of the below login params. + */ +typedef struct iscsi_login_params { + boolean_t immediate_data; + boolean_t initial_r2t; + int first_burst_length; /* range: 512 - 2**24-1 */ + int max_burst_length; /* range: 512 - 2**24-1 */ + boolean_t data_pdu_in_order; + boolean_t data_sequence_in_order; + int default_time_to_wait; + int default_time_to_retain; + int header_digest; + int data_digest; + int max_recv_data_seg_len; /* range: 512 - 2**24-1 */ + int max_xmit_data_seg_len; /* range: 512 - 2**24-1 */ + int max_connections; + int max_outstanding_r2t; + int error_recovery_level; + boolean_t ifmarker; + boolean_t ofmarker; +} iscsi_login_params_t; + +/* + * Once parameters have been set via ISCSI_SET_PARAM the login is initiated + * by sending an ISCSI_LOGIN ioctl with the following structure filled in. + */ +typedef struct entry { + int e_vers; + uint32_t e_oid; + union { + struct in_addr u_in4; + struct in6_addr u_in6; + } e_u; + /* + * e_insize indicates which of the previous structs is valid. + */ + int e_insize; + int e_port; + int e_tpgt; +} entry_t; + +/* + * Used when setting or gettnig the Initiator Name or Alias. + */ +typedef struct node_name { + unsigned char n_name[ISCSI_MAX_NAME_LEN]; + int n_len; +} node_name_t; + +typedef struct _iSCSIMinMaxValue { + uint32_t i_current, + i_default, + i_min, + i_max, + i_incr; +} iscsi_int_info_t; + +typedef struct _iSCSIBoolValue { + boolean_t b_current, + b_default; +} iscsi_bool_info_t; + +typedef struct _iSCSIParamValueGet { + boolean_t v_valid, + v_settable; + iscsi_int_info_t v_integer; + iscsi_bool_info_t v_bool; + uchar_t v_name[ISCSI_MAX_NAME_LEN]; +} iscsi_get_value_t; + +typedef struct _iSCSILoginParamGet { + uint32_t g_vers; /* In */ + uint32_t g_oid; /* In */ + uint32_t g_param; /* Out */ + iscsi_get_value_t g_value; /* Out */ + uint32_t g_conn_cid; /* In */ + + /* + * To indicate whether session or connection related param is + * being requested. + */ + uint32_t g_param_type; /* In */ +} iscsi_param_get_t; + +typedef struct iscsi_set_value { + uint32_t v_integer; + boolean_t v_bool; + uchar_t v_name[ISCSI_MAX_NAME_LEN]; +} iscsi_set_value_t; + +/* + * All of the members of this structure are set by the user agent and + * consumed by the driver. + */ +typedef struct iSCSILoginParamSet { + uint32_t s_vers, + s_oid; + uint32_t s_param; + iscsi_set_value_t s_value; +} iscsi_param_set_t; + +/* + * Data in this structure is set by the user agent and consumed by + * the driver. + */ +typedef struct chap_props { + uint32_t c_vers, + c_retries, + c_oid; + unsigned char c_user[128]; + uint32_t c_user_len; + unsigned char c_secret[16]; + uint32_t c_secret_len; +} iscsi_chap_props_t; + +typedef enum authMethod { + authMethodNone = 0x00, + authMethodCHAP = 0x01, + authMethodSRP = 0x02, + authMethodKRB5 = 0x04, + authMethodSPKM1 = 0x08, + authMethodSPKM2 = 0x10 +} authMethod_t; + +/* + * Data in this structure is set by the user agent and consumed by + * the driver. + */ +typedef struct auth_props { + uint32_t a_vers; + uint32_t a_oid; + boolean_t a_bi_auth; + authMethod_t a_auth_method; +} iscsi_auth_props_t; + +/* + * Data in this structure is set by the user agent and consumed by + * the driver. + */ +#define MAX_RAD_SHARED_SECRET_LEN 128 +typedef struct radius_props { + uint32_t r_vers; + uint32_t r_oid; + union { + struct in_addr u_in4; + struct in6_addr u_in6; + } r_addr; + /* + * r_insize indicates which of the previous structs is valid. + */ + int r_insize; + + uint32_t r_port; + uint8_t r_shared_secret[MAX_RAD_SHARED_SECRET_LEN]; + boolean_t r_radius_access; + boolean_t r_radius_config_valid; + uint32_t r_shared_secret_len; +} iscsi_radius_props_t; + +typedef struct _IPAddress { + union { + struct in_addr in4; + struct in6_addr in6; + } i_addr; + /* i_insize determines which is valid in the union above */ + int i_insize; +} iscsi_ipaddr_t; + +typedef struct _iSCSITargetAddressKey { + iscsi_ipaddr_t a_addr; + uint32_t a_port, + a_oid; +} iscsi_addr_t; + +typedef struct _iSCSITargetAddressKeyProperties { + uint32_t al_vers, /* In */ + al_oid; /* In */ + uint32_t al_in_cnt; /* In */ + uint32_t al_out_cnt; /* Out */ + uint32_t al_tpgt; /* Out */ + iscsi_addr_t al_addrs[1]; /* Out */ +} iscsi_addr_list_t; + +typedef struct _iSCSITargetProperties { + uint32_t p_vers, /* In */ + p_oid; /* In */ + uchar_t p_name[ISCSI_MAX_NAME_LEN]; /* Out */ + uint_t p_name_len; /* Out */ + uchar_t p_alias[ISCSI_MAX_NAME_LEN]; /* Out */ + uint_t p_alias_len; /* Out */ + iSCSIDiscoveryMethod_t p_discovery; /* Out */ + boolean_t p_connected; /* Out */ + uint32_t p_num_of_connections; /* Out */ + /* ---- If connected == B_TRUE then lastErr has no meaning. ---- */ + iscsi_error_t p_last_err; /* Out */ + /* + * Target portal group tag = -1 value means default. + */ + int p_tpgt_conf; /* Out */ + int p_tpgt_nego; /* Out */ + uchar_t p_isid[ISCSI_ISID_LEN]; /* Out */ + uchar_t p_reserved[128]; +} iscsi_property_t; + +typedef struct _iSCSITargetDeviceList { + uint32_t tl_vers, /* In */ + tl_in_cnt, /* In */ + tl_tgt_list_type, /* In */ + tl_out_cnt, /* Out */ + tl_oid_list[1]; /* Out */ +} iscsi_target_list_t; + +typedef struct _iSCSIStaticTargetProperties { + uint32_t p_vers, /* In */ + p_oid; /* In */ + uchar_t p_name[ISCSI_MAX_NAME_LEN]; /* Out */ + uint_t p_name_len; /* Out */ + iscsi_addr_list_t p_addr_list; /* Out */ +} iscsi_static_property_t; + +typedef enum iscsi_lun_status { + LunValid, LunDoesNotExist +} iscsi_lun_status_t; + +/* + * SCSI inquiry vendor and product identifier buffer length - these values are + * defined by the identifier length plus 1 byte for the + * null termination. + */ +#define ISCSI_INQ_VID_BUF_LEN 9 /* 8 byte ID */ +#define ISCSI_INQ_PID_BUF_LEN 17 /* 16 byte ID */ + +typedef struct iscsi_lun_props { + uint32_t lp_vers, /* In */ + lp_tgt_oid, /* In */ + lp_oid, /* In */ + lp_num, /* Out */ + lp_status; /* Out */ + char lp_pathname[MAXPATHLEN], /* Out */ + lp_vid[ISCSI_INQ_VID_BUF_LEN], /* Out */ + lp_pid[ISCSI_INQ_PID_BUF_LEN]; /* Out */ + time_t lp_time_online; /* Out */ +} iscsi_lun_props_t; + +typedef struct iscsi_if_lun { + uint32_t l_tgt_oid, + l_oid, + l_num; +} iscsi_if_lun_t; + +typedef struct iscsi_lun_list { + uint32_t ll_vers; /* In */ + boolean_t ll_all_tgts; /* In */ + uint32_t ll_tgt_oid, /* In */ + ll_in_cnt, /* In */ + ll_out_cnt; /* Out */ + iscsi_if_lun_t ll_luns[1]; /* Out */ +} iscsi_lun_list_t; + +typedef struct iscsi_conn_props { + uint32_t cp_vers, /* In */ + cp_oid, /* In */ + cp_cid, /* In */ + cp_sess_oid; /* In */ + union { + struct sockaddr_in soa4; + struct sockaddr_in6 soa6; + } cp_local; /* Out */ + union { + struct sockaddr_in soa4; + struct sockaddr_in6 soa6; + } cp_peer; /* Out */ + + + iscsi_login_params_t cp_params; + boolean_t cp_params_valid; + +} iscsi_conn_props_t; + +typedef struct iscsi_if_conn { + uint32_t c_sess_oid, + c_oid, + c_cid; +} iscsi_if_conn_t; + +typedef struct iscsi_conn_list { + uint32_t cl_vers; /* In */ + boolean_t cl_all_sess; /* In */ + uint32_t cl_sess_oid, /* In */ + cl_in_cnt, /* In */ + cl_out_cnt; /* Out */ + iscsi_if_conn_t cl_list[1]; /* Out */ +} iscsi_conn_list_t; + +typedef enum iSNSDiscoveryMethod { + iSNSDiscoveryMethodStatic = 0, + iSNSDiscoveryMethodDHCP = 1, + iSNSDiscoveryMethodSLP = 2 +} isns_method_t; + +typedef struct iSCSIDiscoveryProperties { + uint32_t vers; + boolean_t iSNSDiscoverySettable; + boolean_t iSNSDiscoveryEnabled; + isns_method_t iSNSDiscoveryMethod; + unsigned char iSNSDomainName[256]; + boolean_t SLPDiscoverySettable; + boolean_t SLPDiscoveryEnabled; + boolean_t StaticDiscoverySettable; + boolean_t StaticDiscoveryEnabled; + boolean_t SendTargetsDiscoverySettable; + boolean_t SendTargetsDiscoveryEnabled; +} iSCSIDiscoveryProperties_t; + +typedef struct iscsi_uscsi { + uint32_t iu_vers; + uint32_t iu_oid; + int iu_tpgt; + uint32_t iu_len; + uint32_t iu_lun; + struct uscsi_cmd iu_ucmd; +} iscsi_uscsi_t; + +#if defined(_SYSCALL32) +typedef struct iscsi_uscsi32 { + uint32_t iu_vers; + uint32_t iu_oid; + int iu_tpgt; + uint32_t iu_len; + uint32_t iu_lun; + struct uscsi_cmd32 iu_ucmd; +} iscsi_uscsi32_t; +#endif /* _SYSCALL32 */ + +typedef struct iscsi_sendtgts_entry { + /* ---- Node name, NULL terminated UTF-8 string ---- */ + uchar_t ste_name[ISCSI_MAX_NAME_LEN]; + + iscsi_addr_t ste_ipaddr; + int ste_tpgt; +} iscsi_sendtgts_entry_t; + +typedef struct iscsi_sendtgts_list { + entry_t stl_entry; /* In */ + uint32_t stl_in_cnt, /* In */ + stl_out_cnt; /* Out */ + iscsi_sendtgts_entry_t stl_list[1]; /* Out */ +} iscsi_sendtgts_list_t; + +typedef struct iscsi_statictgt_entry { + entry_t te_entry; /* In */ + uchar_t te_name[ISCSI_MAX_NAME_LEN]; /* In */ +} iscsi_target_entry_t; + +/* iSNS Draft - section 4.1.1. */ +typedef struct isns_portal_group { + uint8_t pg_iscsi_name[ISCSI_MAX_NAME_LEN]; + union { + in_addr_t u_ip4; + in6_addr_t u_ip6; + } pg_ip_addr; + int insize; + + in_port_t pg_port; + uint16_t pg_tag; + + iscsi_ipaddr_t isns_server_ip; + uint32_t isns_server_port; +} isns_portal_group_t; + +typedef struct isns_portal_group_list { + uint32_t pg_in_cnt, + pg_out_cnt; + isns_portal_group_t pg_list[1]; +} isns_portal_group_list_t; + +typedef struct isns_server_portal_group_list { + iscsi_addr_t addr; + isns_portal_group_list_t addr_port_list; +} isns_server_portal_group_list_t; + +#define ISCSI_MIN_CONFIG_SESSIONS 1 +/* lowered max config sessions due to ct_power_cnt >= 0 assert */ +#define ISCSI_MAX_CONFIG_SESSIONS 4 + +typedef struct iscsi_config_sess { + uint32_t ics_ver; + uint32_t ics_oid; + boolean_t ics_bound; + uint_t ics_in; + uint_t ics_out; + iscsi_ipaddr_t ics_bindings[1]; +} iscsi_config_sess_t; + +#define ISCSI_SESSION_CONFIG_SIZE(SIZE) \ + (sizeof (iscsi_config_sess_t) + \ + ((SIZE - 1) * sizeof (iscsi_ipaddr_t))) + +/* + * Event class and subclass information + */ +#define EC_ISCSI "EC_iSCSI" +#define ESC_ISCSI_STATIC_START "ESC_static_start" +#define ESC_ISCSI_STATIC_END "ESC_static_end" +#define ESC_ISCSI_SEND_TARGETS_START "ESC_send_targets_start" +#define ESC_ISCSI_SEND_TARGETS_END "ESC_send_targets_end" +#define ESC_ISCSI_SLP_START "ESC_slp_start" +#define ESC_ISCSI_SLP_END "ESC_slp_end" +#define ESC_ISCSI_ISNS_START "ESC_isns_start" +#define ESC_ISCSI_ISNS_END "ESC_isns_end" +#define ESC_ISCSI_PROP_CHANGE "ESC_prop_change" + +#ifdef _KERNEL +/* ---- iscsi_utils.c ---- */ +extern int iscsid_open(char *, int, int); +extern int iscsid_close(int); +extern int iscsid_remove(char *filename); +extern int iscsid_rename(char *oldname, char *newname); +extern ssize_t iscsid_write(int, void *, ssize_t); +extern ssize_t iscsid_read(int, void *, ssize_t); +extern ssize_t iscsid_sendto(struct sonode *, void *, size_t, + struct sockaddr *, socklen_t); +extern ssize_t iscsid_recvfrom(struct sonode *, void *buffer, + size_t len); +extern int iscsid_errno; +#endif + +/* + * Function prototypes for those routines found in the common code + */ +/* ---- utils.c ---- */ +extern boolean_t utils_iqn_create(char *, int); +extern char *prt_bitmap(int, char *, char *, int); +extern char *utils_map_param(int); +extern boolean_t parse_addr_port_tpgt(char *in, char **addr, + int *type, char **port, char **tpgt); + +#ifdef __cplusplus +} +#endif + +#endif /* _ISCSI_IF_H */ diff --git a/usr/src/uts/common/sys/iscsit/iscsit_common.h b/usr/src/uts/common/sys/iscsit/iscsit_common.h new file mode 100644 index 000000000000..1675fedb2da9 --- /dev/null +++ b/usr/src/uts/common/sys/iscsit/iscsit_common.h @@ -0,0 +1,292 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ISCSIT_COMMON_H_ +#define _ISCSIT_COMMON_H_ + +#ifdef _KERNEL +#include +#else +#include +#endif +/* + * XXX Need to reverse this dependency, libiscsit.h should include + * iscsit_common.h, and iscsit_common.h should have all the core + * definitions. Kernel drivers should not pull in library header files. + */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define ISCSIT_API_VERS0 0 + +typedef enum { + ITCFG_SUCCESS = 0, + ITCFG_INVALID, + ITCFG_TGT_CREATE_ERR, + ITCFG_MISC_ERR +} it_cfg_status_t; + +/* + * This structure is passed back to the driver during ISCSIT_IOC_ENABLE_SVC + * in order to provide the fully qualified hostname for use as the EID + * by iSNS. + */ + +#define ISCSIT_MAX_HOSTNAME_LEN 256 + +typedef struct iscsit_hostinfo_s { + uint32_t length; + char fqhn[ISCSIT_MAX_HOSTNAME_LEN]; +} iscsit_hostinfo_t; + +#define ISCSIT_IOC_SET_CONFIG 1 +#define ISCSIT_IOC_GET_STATE 2 +#define ISCSIT_IOC_ENABLE_SVC 101 +#define ISCSIT_IOC_DISABLE_SVC 102 + +/* XXX Rationalize these with other error values (used in it_smf.c */ +#define ITADM_SUCCESS 0 +#define ITADM_FATAL_ERROR 0x1 +#define ITADM_NO_MEM 0x2 +#define ITADM_INVALID 0x4 +#define ITADM_NODATA 0x8 +#define ITADM_PERM 0x10 + + +#define PROP_AUTH "auth" +#define PROP_ALIAS "alias" +#define PROP_CHAP_USER "chapuser" +#define PROP_CHAP_SECRET "chapsecret" +#define PROP_TARGET_CHAP_USER "targetchapuser" +#define PROP_TARGET_CHAP_SECRET "targetchapsecret" +#define PROP_RADIUS_SERVER "radiusserver" +#define PROP_RADIUS_SECRET "radiussecret" +#define PROP_ISNS_ENABLED "isns" +#define PROP_ISNS_SERVER "isnsserver" +#define PROP_OLD_TARGET_NAME "oldtargetname" + +#define PA_AUTH_RADIUS "radius" +#define PA_AUTH_CHAP "chap" +#define PA_AUTH_NONE "none" + +typedef struct { + int set_cfg_vers; + int set_cfg_pnvlist_len; + caddr_t set_cfg_pnvlist; +} iscsit_ioc_set_config_t; + +typedef struct { + int getst_vers; + int getst_pnvlist_len; + char *getst_pnvlist; +} iscsit_ioc_getstate_t; + +#ifdef _SYSCALL32 +typedef struct { + int set_cfg_vers; + int set_cfg_pnvlist_len; + caddr32_t set_cfg_pnvlist; +} iscsit_ioc_set_config32_t; + +typedef struct { + int getst_vers; + int getst_pnvlist_len; + caddr32_t getst_pnvlist; +} iscsit_ioc_getstate32_t; +#endif /* _SYSCALL32 */ + +/* Functions to convert iSCSI target structures to/from nvlists. */ +int +it_config_to_nv(it_config_t *cfg, nvlist_t **nvl); + +/* + * nvlist version of config is 3 list-of-list, + 1 proplist. arrays + * are interesting, but lists-of-lists are more useful when doing + * individual lookups when we later add support for it. Also, no + * need to store name in individual struct representation. + */ +int +it_nv_to_config(nvlist_t *nvl, it_config_t **cfg); + +int +it_nv_to_tgtlist(nvlist_t *nvl, uint32_t *count, it_tgt_t **tgtlist); + +int +it_tgtlist_to_nv(it_tgt_t *tgtlist, nvlist_t **nvl); + +int +it_tgt_to_nv(it_tgt_t *tgt, nvlist_t **nvl); + +int +it_nv_to_tgt(nvlist_t *nvl, char *name, it_tgt_t **tgt); + +int +it_tpgt_to_nv(it_tpgt_t *tpgt, nvlist_t **nvl); + +int +it_nv_to_tpgt(nvlist_t *nvl, char *name, it_tpgt_t **tpgt); + +int +it_tpgtlist_to_nv(it_tpgt_t *tpgtlist, nvlist_t **nvl); + +int +it_nv_to_tpgtlist(nvlist_t *nvl, uint32_t *count, it_tpgt_t **tpgtlist); + +int +it_tpg_to_nv(it_tpg_t *tpg, nvlist_t **nvl); + +int +it_nv_to_tpg(nvlist_t *nvl, char *name, it_tpg_t **tpg); + +int +it_tpglist_to_nv(it_tpg_t *tpglist, nvlist_t **nvl); + +int +it_nv_to_tpglist(nvlist_t *nvl, uint32_t *count, it_tpg_t **tpglist); + +int +it_ini_to_nv(it_ini_t *ini, nvlist_t **nvl); + +int +it_nv_to_ini(nvlist_t *nvl, char *name, it_ini_t **ini); + +int +it_inilist_to_nv(it_ini_t *inilist, nvlist_t **nvl); + +int +it_nv_to_inilist(nvlist_t *nvl, uint32_t *count, it_ini_t **inilist); + +it_tgt_t * +it_tgt_lookup(it_config_t *cfg, char *tgt_name); + +it_tpg_t * +it_tpg_lookup(it_config_t *cfg, char *tpg_name); + +it_portal_t * +it_sns_svr_lookup(it_config_t *cfg, struct sockaddr_storage *sa); + +it_portal_t * +it_portal_lookup(it_tpg_t *cfg_tpg, struct sockaddr_storage *sa); + +int +it_sa_compare(struct sockaddr_storage *sa1, struct sockaddr_storage *sa2); + +/* + * Convert a sockaddr to the string representation, suitable for + * storing in an nvlist or printing out in a list. + */ +int +sockaddr_to_str(struct sockaddr_storage *sa, char **addr); + +/* + * Convert a char string to a sockaddr structure + * + * default_port should be the port to be used, if not specified + * as part of the supplied string 'arg'. + */ +struct sockaddr_storage * +it_common_convert_sa(char *arg, struct sockaddr_storage *buf, + uint32_t default_port); + +/* + * Convert an string array of IP-addr:port to a portal list + */ +int +it_array_to_portallist(char **arr, uint32_t count, uint32_t default_port, + it_portal_t **portallist, uint32_t *list_count); + +/* + * Function: it_config_free_cmn() + * + * Free any resources associated with the it_config_t structure. + * + * Parameters: + * cfg A C representation of the current iSCSI configuration + */ +void +it_config_free_cmn(it_config_t *cfg); + +/* + * Function: it_tgt_free_cmn() + * + * Frees an it_tgt_t structure. If tgt_next is not NULL, frees + * all structures in the list. + */ +void +it_tgt_free_cmn(it_tgt_t *tgt); + +/* + * Function: it_tpgt_free_cmn() + * + * Deallocates resources of an it_tpgt_t structure. If tpgt->next + * is not NULL, frees all members of the list. + */ +void +it_tpgt_free_cmn(it_tpgt_t *tpgt); + +/* + * Function: it_tpg_free_cmn() + * + * Deallocates resources associated with an it_tpg_t structure. + * If tpg->next is not NULL, frees all members of the list. + */ +void +it_tpg_free_cmn(it_tpg_t *tpg); + +/* + * Function: it_ini_free_cmn() + * + * Deallocates resources of an it_ini_t structure. If ini->next is + * not NULL, frees all members of the list. + */ +void +it_ini_free_cmn(it_ini_t *ini); + +/* + * Function: iscsi_binary_to_base64_str() + * + * Encodes a byte array into a base64 string. + */ +int +iscsi_binary_to_base64_str(uint8_t *in_buf, int in_buf_len, + char *base64_str_buf, int base64_buf_len); + +/* + * Function: iscsi_base64_str_to_binary() + * + * Decodes a base64 string into a byte array + */ +int +iscsi_base64_str_to_binary(char *hstr, int hstr_len, + uint8_t *binary, int binary_buf_len, int *out_len); + +#ifdef __cplusplus +} +#endif + +#endif /* _ISCSIT_COMMON_H_ */ diff --git a/usr/src/uts/common/sys/iscsit/isns_protocol.h b/usr/src/uts/common/sys/iscsit/isns_protocol.h new file mode 100644 index 000000000000..0480fd005246 --- /dev/null +++ b/usr/src/uts/common/sys/iscsit/isns_protocol.h @@ -0,0 +1,281 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _ISNS_PROTOCOL_H +#define _ISNS_PROTOCOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#define ISNSP_VERSION (0x01) + +#define ISNS_DEFAULT_SERVER_PORT (3205) + +#define ISNSP_HEADER_SIZE (12) +#define ISNSP_RSP_CODE_SIZE (4) +#define ISNSP_MAX_PAYLOAD_SIZE (65532) +#define ISNSP_MAX_PDU_SIZE (ISNSP_HEADER_SIZE + \ + ISNSP_MAX_PAYLOAD_SIZE) + +#define ISNS_TLV_ATTR_ID_LEN (4) +#define ISNS_TLV_ATTR_LEN_LEN (4) +#define MAX_ISNS_MESG_ATTR_ENTRIES (8) +#define MAX_ISNS_OPER_ATTR_ENTRIES (32) + +/* iSNS Entity Protocol, iSNS Draft - section 6.2.2. */ +#define ISNS_ENTITY_PROTOCOL_NO (1) +#define ISNS_ENTITY_PROTOCOL_ISCSI (2) +#define ISNS_ENTITY_PROTOCOL_FCP (3) + +/* iSNS Function IDs, iSNS Draft - section 4.1.3. */ +#define ISNS_DEV_ATTR_REG (0x0001) +#define ISNS_DEV_ATTR_QRY (0x0002) +#define ISNS_DEV_GET_NEXT (0x0003) +#define ISNS_DEV_DEREG (0x0004) +#define ISNS_SCN_REG (0x0005) +#define ISNS_SCN_DEREG (0x0006) +#define ISNS_SCN_EVENT (0x0007) +#define ISNS_SCN (0x0008) +#define ISNS_DD_REG (0x0009) +#define ISNS_DD_DEREG (0x000A) +#define ISNS_DDS_REG (0x000B) +#define ISNS_DDS_DEREG (0x000C) +#define ISNS_ESI (0x000D) +#define ISNS_HEARTBEAT (0x000E) +#define ISNS_DEV_ATTR_REG_RSP (0x8001) +#define ISNS_DEV_ATTR_QRY_RSP (0x8002) +#define ISNS_DEV_GET_NEXT_RSP (0x8003) +#define ISNS_DEV_DEREG_RSP (0x8004) +#define ISNS_SCN_REG_RSP (0x8005) +#define ISNS_SCN_DEREG_RSP (0x8006) +#define ISNS_SCN_EVENT_RSP (0x8007) +#define ISNS_SCN_RSP (0x8008) +#define ISNS_DD_REG_RSP (0x8009) +#define ISNS_DD_DEREG_RSP (0x800A) +#define ISNS_DDS_REG_RSP (0x800B) +#define ISNS_DDS_DEREG_RSP (0x800C) +#define ISNS_ESI_RSP (0x800D) + +/* iSNS Flags, iSNS Draft - section 5.1.4. */ +#define ISNS_FLAG_FIRST_PDU (0x0400) +#define ISNS_FLAG_LAST_PDU (0x0800) +#define ISNS_FLAG_REPLACE_REG (0x1000) +#define ISNS_FLAG_AUTH_BLK_PRESENTED (0x2000) +#define ISNS_FLAG_SERVER (0x4000) +#define ISNS_FLAG_CLIENT (0x8000) + +/* iSNS Response Status, iSNS Draft - section 5.4 */ +#define ISNS_RSP_SUCCESSFUL (0) +#define ISNS_RSP_UNKNOWN_ERROR (1) +#define ISNS_RSP_MSG_FORMAT_ERROR (2) +#define ISNS_RSP_INVALID_REGIS (3) +#define ISNS_RSP_INVALID_QRY (5) +#define ISNS_RSP_SRC_UNKNOWN (6) +#define ISNS_RSP_SRC_ABSENT (7) +#define ISNS_RSP_SRC_UNAUTHORIZED (8) +#define ISNS_RSP_NO_SUCH_ENTRY (9) +#define ISNS_RSP_VER_NOT_SUPPORTED (10) +#define ISNS_RSP_INTERNAL_ERROR (11) +#define ISNS_RSP_BUSY (12) +#define ISNS_RSP_OPTION_NOT_UNDERSTOOD (13) +#define ISNS_RSP_INVALID_UPDATE (14) +#define ISNS_RSP_MSG_NOT_SUPPORTED (15) +#define ISNS_RSP_SCN_EVENT_REJECTED (16) +#define ISNS_RSP_SCN_REGIS_REJECTED (17) +#define ISNS_RSP_ATTR_NOT_IMPL (18) +#define ISNS_RSP_ESI_NOT_AVAILABLE (21) +#define ISNS_RSP_INVALID_DEREGIS (22) +#define ISNS_RSP_REGIS_NOT_SUPPORTED (23) + +/* iSNS Attribute IDs, iSNS Draft - section 6.1. */ +#define ISNS_DELIMITER_ATTR_ID (0) +#define ISNS_EID_ATTR_ID (1) +#define ISNS_ENTITY_PROTOCOL_ATTR_ID (2) +#define ISNS_MGMT_IP_ADDR_ATTR_ID (3) +#define ISNS_TIMESTAMP_ATTR_ID (4) +#define ISNS_VERSION_RANGE_ATTR_ID (5) +#define ISNS_ENTITY_REG_PERIOD_ATTR_ID (6) +#define ISNS_ENTITY_INDEX_ATTR_ID (7) +#define ISNS_ENTITY_NEXT_INDEX_ATTR_ID (8) +#define ISNS_ENTITY_ISAKMP_P1_ATTR_ID (11) +#define ISNS_ENTITY_CERT_ATTR_ID (12) +#define ISNS_PORTAL_IP_ADDR_ATTR_ID (16) +#define ISNS_PORTAL_PORT_ATTR_ID (17) +#define ISNS_PORTAL_NAME_ATTR_ID (18) +#define ISNS_ESI_INTERVAL_ATTR_ID (19) +#define ISNS_ESI_PORT_ATTR_ID (20) +#define ISNS_PORTAL_INDEX_ATTR_ID (22) +#define ISNS_SCN_PORT_ATTR_ID (23) +#define ISNS_PORTAL_NEXT_INDEX_ATTR_ID (24) +#define ISNS_PORTAL_SEC_BMP_ATTR_ID (27) +#define ISNS_PORTAL_ISAKMP_P1_ATTR_ID (28) +#define ISNS_PORTAL_ISAKMP_P2_ATTR_ID (29) +#define ISNS_PORTAL_CERT_ATTR_ID (31) +#define ISNS_ISCSI_NAME_ATTR_ID (32) +#define ISNS_ISCSI_NODE_TYPE_ATTR_ID (33) +#define ISNS_ISCSI_ALIAS_ATTR_ID (34) +#define ISNS_ISCSI_SCN_BITMAP_ATTR_ID (35) +#define ISNS_ISCSI_NODE_INDEX_ATTR_ID (36) +#define ISNS_WWNN_TOKEN_ATTR_ID (37) +#define ISNS_NODE_NEXT_INDEX_ATTR_ID (38) +#define ISNS_ISCSI_AUTH_METHOD_ATTR_ID (42) +#define ISNS_PG_ISCSI_NAME_ATTR_ID (48) +#define ISNS_PG_PORTAL_IP_ADDR_ATTR_ID (49) +#define ISNS_PG_PORTAL_PORT_ATTR_ID (50) +#define ISNS_PG_TAG_ATTR_ID (51) +#define ISNS_PG_INDEX_ATTR_ID (52) +#define ISNS_PG_NEXT_ID_ATTR_ID (53) +#define ISNS_DD_SET_ID_ATTR_ID (2049) +#define ISNS_DD_SET_NAME_ATTR_ID (2050) +#define ISNS_DD_SET_STATUS_ATTR_ID (2051) +#define ISNS_DD_ID_ATTR_ID (2065) +#define ISNS_DD_NAME_ATTR_ID (2066) +#define ISNS_DD_ISCSI_INDEX_ATTR_ID (2067) +#define ISNS_DD_ISCSI_NAME_ATTR_ID (2068) +#define ISNS_DD_FC_PORT_NAME_ATTR_ID (2069) +#define ISNS_DD_PORTAL_INDEX_ATTR_ID (2070) +#define ISNS_DD_PORTAL_IP_ADDR_ATTR_ID (2071) +#define ISNS_DD_PORTAL_PORT_ATTR_ID (2072) +#define ISNS_DD_FEATURES_ATTR_ID (2078) + +/* Entity Protocol, RFC 4171 - section 6.2.2. */ +#define ISNS_ENTITY_NO_PROTOCOL (1) +#define ISNS_ENTITY_ISCSI (2) +#define ISNS_ENTITY_IFCP (3) + +/* Protocol Version Range, RFC 4171 - section 6.2.5. */ +#define ISNS_VER_SHIFT (16) +#define ISNS_VERSION (0x0000FFFF) + +/* Portal Port, RFC 4171 - section 6.3.2. */ +#define ISNS_PORT_BITS (0x0000FFFF) /* Bits 16 - 31 */ +#define ISNS_PORT_TYPE (0x00010000) /* Bit 15 */ + +/* Portal Security Bitmap, RFC 4171 - section 6.3.9. */ +#define ISNS_TUNNEL_MODE_PREFERRED (0x0040) /* Bit 25 */ +#define ISNS_TRANS_MODE_PREFERRED (0x0020) /* Bit 26 */ +#define ISNS_PFS_ENABLED (0x0010) /* Bit 27 */ +#define ISNS_AGGR_MODE_ENABLED (0x0008) /* Bit 28 */ +#define ISNS_MAIN_MODE_ENABLED (0x0004) /* Bit 29 */ +#define ISNS_IKE_IPSEC_ENABLED (0x0002) /* Bit 30 */ +#define ISNS_BITMAP_VALID (0x0001) /* Bit 31 */ + +/* iSCSI Node Type, RFC 4171 - section 6.4.2. */ +#define ISNS_TARGET_NODE_TYPE (0x0001) +#define ISNS_INITIATOR_NODE_TYPE (0x0002) +#define ISNS_CONTROL_NODE_TYPE (0x0004) + +/* iSCSI Node SCN Bitmap, RFC 4171 - section 6.4.4. */ +#define ISNS_INIT_SELF_INFO_ONLY (0x0080) /* Bit 24 */ +#define ISNS_TARGET_SELF_INFO_ONLY (0x0040) /* Bit 25 */ +#define ISNS_MGMT_REG (0x0020) /* Bit 26 */ +#define ISNS_OBJECT_REMOVED (0x0010) /* Bit 27 */ +#define ISNS_OBJECT_ADDED (0x0008) /* Bit 28 */ +#define ISNS_OBJECT_UPDATED (0x0004) /* Bit 29 */ +#define ISNS_MEMBER_REMOVED (0x0002) /* Bit 30 */ +#define ISNS_MEMBER_ADDED (0x0001) /* Bit 31 */ + +/* Portal Group Tag, RFC 4171 - section 6.5.4. */ +#define ISNS_PG_TAG (0x0000FFFF) /* Bits 16 - 31 */ + +/* DDS Status, RFC 4171 - section 6.11.1.3. */ +#define ISNS_DDS_STATUS (0x0001) /* Bit 31 */ + +/* DD Feature, RFC 4171 - section 6.11.2.9. */ +#define ISNS_DD_BOOTLIST (0x0001) /* Bit 31 */ + +/* iSNS Defaults */ +#define ISNS_DEFAULT_PGT (0x00000001) +#define ISNS_DEFAULT_DD_SET_ID (1) +#define ISNS_DEFAULT_DD_ID (1) + +/* Min/Max length of names */ +#define ISNS_DDS_MAX_NAME_LEN (256) +#define ISNS_DD_MAX_NAME_LEN (256) +#define ISNS_ISCSI_MAX_NAME_LEN (224) +#define ISNS_ISCSI_MAX_ALIAS_LEN (256) +#define ISNS_ENTITY_MIN_EID_LEN (3) +#define ISNS_ENTITY_MAX_EID_LEN (255) + + +typedef struct isns_tlv { + uint32_t attr_id; + uint32_t attr_len; + uint8_t attr_value[1]; +} isns_tlv_t; + +typedef struct isns_packet_data { + uint16_t version; + uint16_t func_id; + uint16_t payload_len; + uint16_t flags; + uint16_t xid; + uint16_t seq; + + int num_of_tlvs; + isns_tlv_t tlvs[MAX_ISNS_OPER_ATTR_ENTRIES]; +} isns_packet_data_t; + +typedef struct isns_reg_mesg { + isns_tlv_t src_attr; + int num_of_mesg_attrs; + isns_tlv_t *mesg_attrs[MAX_ISNS_MESG_ATTR_ENTRIES]; + isns_tlv_t delimiter_attr; + isns_tlv_t *operating_attrs[MAX_ISNS_OPER_ATTR_ENTRIES]; +} isns_reg_mesg_t; + +typedef struct isns_resp_mesg { + uint8_t status[4]; + isns_tlv_t messages_attrs[MAX_ISNS_MESG_ATTR_ENTRIES]; + isns_tlv_t delimiter_attr; + isns_tlv_t operating_attrs[MAX_ISNS_OPER_ATTR_ENTRIES]; +} isns_resp_mesg_t; + +typedef struct isns_pdu { + uint16_t version; + uint16_t func_id; + uint16_t payload_len; + uint16_t flags; + uint16_t xid; + uint16_t seq; + uint8_t payload[1]; +} isns_pdu_t; + +typedef struct isns_resp { + uint32_t status; + uint8_t data[1]; +} isns_resp_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _ISNS_PROTOCOL_H */ diff --git a/usr/src/uts/common/sys/iscsit/radius_packet.h b/usr/src/uts/common/sys/iscsit/radius_packet.h new file mode 100644 index 000000000000..bbf96d5cb21b --- /dev/null +++ b/usr/src/uts/common/sys/iscsit/radius_packet.h @@ -0,0 +1,98 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _RADIUS_PACKET_H +#define _RADIUS_PACKET_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +/* A total of RAD_RCV_TIMEOUT * RAD_RETRY_MAX seconds timeout. */ +#define RAD_RCV_TIMEOUT 5 /* Timeout for receiving RADIUS packet in */ + /* sec. */ +#define RAD_RETRY_MAX 2 /* Max. # of times to retry receiving */ + /* packet. */ + +/* Describes a RADIUS attribute */ +typedef struct radius_attr { + int attr_type_code; /* RADIUS attribute type code, */ + /* e.g. RAD_USER_PASSWORD, etc. */ + int attr_value_len; + uint8_t attr_value[MAX_RAD_ATTR_VALUE_LEN]; +} radius_attr_t; + +/* Describes data fields of a RADIUS packet. */ +typedef struct radius_packet_data { + uint8_t code; /* RADIUS code, section 3, RFC 2865. */ + uint8_t identifier; + uint8_t authenticator[RAD_AUTHENTICATOR_LEN]; + int num_of_attrs; + radius_attr_t attrs[4]; /* For this implementation each */ + /* outbound RADIUS packet will only */ + /* have 3 attributes associated with */ + /* it thus the chosen size should be */ + /* good enough. */ +} radius_packet_data_t; + +/* + * Send a request to a RADIUS server. + * + * Returns > 0 on success, <= 0 on failure . + * + */ +int +iscsit_snd_radius_request(void *socket, + iscsi_ipaddr_t rsvr_ip_addr, + uint32_t rsvr_port, + radius_packet_data_t *packet_data); + +#define RAD_RSP_RCVD_SUCCESS 0 +#define RAD_RSP_RCVD_NO_DATA 1 +#define RAD_RSP_RCVD_TIMEOUT 2 +#define RAD_RSP_RCVD_PROTOCOL_ERR 3 +#define RAD_RSP_RCVD_AUTH_FAILED 4 +/* + * Receives a response from a RADIUS server. + * + * Return receive status. + */ +int +iscsit_rcv_radius_response(void *socket, + uint8_t *shared_secret, + uint32_t shared_secret_len, + uint8_t *req_authenticator, + radius_packet_data_t *resp_data); + +#ifdef __cplusplus +} +#endif + +#endif /* _RADIUS_PACKET_H */ diff --git a/usr/src/uts/common/sys/iscsit/radius_protocol.h b/usr/src/uts/common/sys/iscsit/radius_protocol.h new file mode 100644 index 000000000000..76be62909c9b --- /dev/null +++ b/usr/src/uts/common/sys/iscsit/radius_protocol.h @@ -0,0 +1,83 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _RADIUS_PROTOCOL_H +#define _RADIUS_PROTOCOL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Packet type. RFC 2865 section 4. */ +#define RAD_ACCESS_REQ 1 /* Authentication Request */ +#define RAD_ACCESS_ACPT 2 /* Authentication Accepted */ +#define RAD_ACCESS_REJ 3 /* Authentication Rejected */ + +/* RADIUS Attribute Types. RFC 2865 section 5. */ +#define RAD_USER_NAME 1 +#define RAD_CHAP_PASSWORD 3 +#define RAD_CHAP_CHALLENGE 60 + +/* RFC 2865 Section 3. The Identifier field is one octet. */ +#define RAD_IDENTIFIER_LEN 1 + +/* RFC 2865 Section 5.3. The String field is 16 octets. */ +#define RAD_CHAP_PASSWD_STR_LEN 16 + +/* RFC 2865 Section 3. Authenticator field is 16 octets. */ +#define RAD_AUTHENTICATOR_LEN 16 + +/* RFC 2865 Section 5: 1-253 octets */ +#define MAX_RAD_ATTR_VALUE_LEN 253 + +/* RFC 2865 Section 3. Minimum length 20 octets. */ +#define MIN_RAD_PACKET_LEN 20 + +/* RFC 2865 Section 3. Maximum length 4096 octets. */ +#define MAX_RAD_PACKET_LEN 4096 + +/* Maximum RADIUS shared secret length (in fact there is no defined limit) */ +#define MAX_RAD_SHARED_SECRET_LEN 128 + +/* RFC 2865 Section 3. Minimum RADIUS shared secret length */ +#define MIN_RAD_SHARED_SECRET_LEN 16 + +/* Raw RADIUS packet. RFC 2865 section 3. */ +typedef struct radius_packet { + uint8_t code; /* RADIUS code, section 3, RFC 2865 */ + uint8_t identifier; /* 1 octet in length. RFC 2865 section 3 */ + uint8_t length[2]; /* 2 octets, or sizeof (u_short) */ + uint8_t authenticator[RAD_AUTHENTICATOR_LEN]; + uint8_t data[1]; +} radius_packet_t; + +/* Length of a RADIUS packet minus the payload */ +#define RAD_PACKET_HDR_LEN 20 + +#ifdef __cplusplus +} +#endif + +#endif /* _RADIUS_PROTOCOL_H */ diff --git a/usr/src/uts/intel/Makefile.intel.shared b/usr/src/uts/intel/Makefile.intel.shared index 8608fcf00d70..22d63f05ae01 100644 --- a/usr/src/uts/intel/Makefile.intel.shared +++ b/usr/src/uts/intel/Makefile.intel.shared @@ -329,6 +329,7 @@ DRV_KMODS += stmf DRV_KMODS += stmf_sbd DRV_KMODS += fct DRV_KMODS += qlt +DRV_KMODS += iscsit DRV_KMODS += ncall nsctl sdbc nskern sv DRV_KMODS += ii rdc rdcsrv rdcstub @@ -552,6 +553,7 @@ MISC_KMODS += ibcm MISC_KMODS += ibdm MISC_KMODS += ibmf MISC_KMODS += ibtl +MISC_KMODS += idm MISC_KMODS += idmap MISC_KMODS += iommulib MISC_KMODS += ipc diff --git a/usr/src/uts/intel/idm/Makefile b/usr/src/uts/intel/idm/Makefile new file mode 100644 index 000000000000..463a8be02a10 --- /dev/null +++ b/usr/src/uts/intel/idm/Makefile @@ -0,0 +1,89 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# uts/intel/io/idm/Makefile +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This makefile drives the production of the idm kernel module. +# +# intel architecture dependent +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = idm +OBJECTS = $(IDM_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(IDM_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_MISC_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/intel/Makefile.intel + +# +# Define targets +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +# +# Overrides. +# +DEBUG_FLGS = +DEBUG_DEFS += $(DEBUG_FLGS) +LDFLAGS += -dy -Nfs/sockfs + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/intel/Makefile.targ diff --git a/usr/src/uts/intel/iscsit/Makefile b/usr/src/uts/intel/iscsit/Makefile new file mode 100644 index 000000000000..a9c9af0b4f46 --- /dev/null +++ b/usr/src/uts/intel/iscsit/Makefile @@ -0,0 +1,96 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This makefile drives the production of the iscsit pseudo-driver for +# COMSTAR. + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# + + +UTSBASE = ../.. + +ARCHDIR:sh = cd ..; basename `pwd` + +# +# Define the module and object file sets. +# +MODULE = iscsit +OBJECTS = $(ISCSIT_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(ISCSIT_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE) +CONF_SRCDIR = $(UTSBASE)/common/io/comstar/port/iscsit + +# +# Include common rules. +# +include ../Makefile.$(ARCHDIR) + +# +# Define targets +# +ALL_TARGET = $(BINARY) $(SRC_CONFILE) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE) + +# +# Overrides and depends_on +# +MODSTUBS_DIR = $(OBJS_DIR) +LDFLAGS += -dy -Ndrv/stmf -Nmisc/idm -Nfs/sockfs -Nmisc/md5 + +INC_PATH += -I$(UTSBASE)/common/io/comstar/port/iscsit +# This path should be remove with the libiscsit.h/iscsit_common.h issue is +# resolved. +INC_PATH += -I../../../lib/libiscsit/common/ + +C99MODE= -xc99=%all +C99LMODE= -Xc99=%all + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include ../Makefile.targ diff --git a/usr/src/uts/sparc/Makefile.sparc.shared b/usr/src/uts/sparc/Makefile.sparc.shared index 8f52723273d9..0141a71a810d 100644 --- a/usr/src/uts/sparc/Makefile.sparc.shared +++ b/usr/src/uts/sparc/Makefile.sparc.shared @@ -294,6 +294,7 @@ DRV_KMODS += stmf DRV_KMODS += stmf_sbd DRV_KMODS += fct DRV_KMODS += qlt +DRV_KMODS += iscsit DRV_KMODS += ncall nsctl sdbc nskern sv DRV_KMODS += ii rdc rdcsrv rdcstub DRV_KMODS += iscsi @@ -387,6 +388,7 @@ MISC_KMODS += ibcm MISC_KMODS += ibdm MISC_KMODS += ibmf MISC_KMODS += ibtl +MISC_KMODS += idm MISC_KMODS += idmap MISC_KMODS += hook MISC_KMODS += neti diff --git a/usr/src/uts/sparc/idm/Makefile b/usr/src/uts/sparc/idm/Makefile new file mode 100644 index 000000000000..6b03fb56df90 --- /dev/null +++ b/usr/src/uts/sparc/idm/Makefile @@ -0,0 +1,87 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# uts/intel/io/idm/Makefile +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# +# This makefile drives the production of the idm kernel module. +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = idm +OBJECTS = $(IDM_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(IDM_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_MISC_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/sparc/Makefile.sparc + +# +# Define targets +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +# +# Overrides. +# +DEBUG_FLGS = +DEBUG_DEFS += $(DEBUG_FLGS) +LDFLAGS += -dy -Nfs/sockfs + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/intel/Makefile.targ diff --git a/usr/src/uts/sparc/iscsit/Makefile b/usr/src/uts/sparc/iscsit/Makefile new file mode 100644 index 000000000000..a9c9af0b4f46 --- /dev/null +++ b/usr/src/uts/sparc/iscsit/Makefile @@ -0,0 +1,96 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This makefile drives the production of the iscsit pseudo-driver for +# COMSTAR. + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# + + +UTSBASE = ../.. + +ARCHDIR:sh = cd ..; basename `pwd` + +# +# Define the module and object file sets. +# +MODULE = iscsit +OBJECTS = $(ISCSIT_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(ISCSIT_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE) +CONF_SRCDIR = $(UTSBASE)/common/io/comstar/port/iscsit + +# +# Include common rules. +# +include ../Makefile.$(ARCHDIR) + +# +# Define targets +# +ALL_TARGET = $(BINARY) $(SRC_CONFILE) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE) + +# +# Overrides and depends_on +# +MODSTUBS_DIR = $(OBJS_DIR) +LDFLAGS += -dy -Ndrv/stmf -Nmisc/idm -Nfs/sockfs -Nmisc/md5 + +INC_PATH += -I$(UTSBASE)/common/io/comstar/port/iscsit +# This path should be remove with the libiscsit.h/iscsit_common.h issue is +# resolved. +INC_PATH += -I../../../lib/libiscsit/common/ + +C99MODE= -xc99=%all +C99LMODE= -Xc99=%all + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include ../Makefile.targ