idm_so.c revision 0f94976e74ac54bd0c370887c9aa0838c90b539e
/*
* 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
* 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 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2013 by Delphix. All rights reserved.
*/
#include <sys/sysmacros.h>
#include <sys/socketvar.h>
#include <sys/pathname.h>
#include <sys/iscsi_protocol.h>
#define IN_PROGRESS_DELAY 1
/*
* in6addr_any is currently all zeroes, but use the macro in case this
* ever changes.
*/
/*
* Transport ops prototypes
*/
/*
* IDM Native Sockets transport operations
*/
static
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_declare_key_values /* it_declare_key_values */
};
/*
* idm_so_init()
* Sockets transport initialization
*/
void
{
/* Cache for IDM Data and R2T Transmit PDU's */
/* Cache for IDM Receive PDU's */
/* 128k buffer cache */
/* Set the sockets transport ops */
}
/*
* idm_so_fini()
* Sockets transport teardown
*/
void
idm_so_fini(void)
{
}
{
CRED())) {
return (ks);
} else {
return (NULL);
}
}
/*
* 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_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
{
}
/*
* Function to compare two addresses in sockaddr_storage format
*/
int
const struct sockaddr_storage *cmp_ss2,
{
int i;
/*
* Normalize V4-mapped IPv6 addresses into V4 format if
* v4_mapped_as_v4 is B_TRUE.
*/
if (IN6_IS_ADDR_V4MAPPED(in61)) {
ss1 = &mapped_v4_ss1;
}
}
if (IN6_IS_ADDR_V4MAPPED(in62)) {
ss2 = &mapped_v4_ss2;
}
}
/*
* Compare ports, then address family, then ip address
*/
if (compare_ports &&
return (1);
else
return (-1);
}
/*
* ports are the same
*/
return (1);
else
return (-1);
}
/*
* address families are the same
*/
return (1);
return (-1);
else
return (0);
for (i = 0; i < 4; i++) {
return (1);
return (-1);
}
return (0);
}
return (1);
}
/*
* IP address filter functions to flag addresses that should not
* go out to initiators through discovery.
*/
static boolean_t
{
if ((INADDR_NONE == addr) ||
(IN_MULTICAST(addr)) ||
((addr >> IN_CLASSA_NSHIFT) == 0) ||
return (B_FALSE);
}
return (B_TRUE);
}
static boolean_t
{
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
{
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;
int size_ipaddr;
size_ipaddr = 0;
/* create an ipv4 and ipv6 UDP socket */
return (0);
return (0);
}
/* snapshot the current number of interfaces */
lifn.lifn_count = 0;
/* use vp6 for ioctls with unspecified families by default */
!= 0) {
goto cleanup;
}
if (numifs <= 0) {
goto cleanup;
}
/* allocate extra room in case more interfaces appear */
numifs += 10;
/* get the interface names and ip addresses */
if (rc != 0) {
goto cleanup;
}
/* if our extra room is used up, try again */
goto retry_count;
}
/* calc actual number of ifconfs */
/* get ip address */
if (n > 0) {
size_ipaddr = sizeof (idm_addr_list_t) +
(n - 1) * sizeof (idm_addr_t);
} else {
goto cleanup;
}
/*
* Examine the array of interfaces and filter uninteresting ones
*/
/*
* Copy the address as the SIOCGLIFFLAGS ioctl is destructive
*/
/*
* fetch the flags using the socket of the correct family
*/
case AF_INET:
break;
case AF_INET6:
break;
default:
continue;
}
if (rc == 0) {
/*
* If we got the flags, skip uninteresting
* interfaces based on flags
*/
continue;
if (lp->lifr_flags &
continue;
}
/* save ip address */
case AF_INET:
continue;
break;
case AF_INET6:
continue;
break;
default:
continue;
}
j++;
}
if (j == 0) {
/* no valid ifaddr */
size_ipaddr = 0;
} else {
ipaddr->al_out_cnt = j;
}
return (size_ipaddr);
}
int
{
/*
* Fill in iovec and receive data
*/
}
/*
* 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
{
int error;
/* Initialization of the message header. */
/* Data sent */
/* 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
{
int error;
/* Initialization of the message header. */
== 0) {
/* Data sent */
/* 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
{
int error;
int flags;
/* Initialization of the message header. */
flags = MSG_WAITALL;
== 0) {
/* Received data */
/* 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
{
int conn_abort = 10000;
int conn_notify = 2000;
int abort = 30000;
/* Pre-connect socket options */
TCP_CONN_NOTIFY_THRESHOLD, (char *)&conn_notify, sizeof (int),
CRED());
TCP_CONN_ABORT_THRESHOLD, (char *)&conn_abort, sizeof (int),
CRED());
}
}
static void
{
const int on = 1;
/* Set connect options */
(char *)&idm_so_rcvbuf, sizeof (int), CRED());
(char *)&idm_so_sndbuf, sizeof (int), CRED());
}
static uint32_t
{
}
static idm_status_t
{
void *new_hdr;
int ahslen = 0;
int total_len = 0;
int iovlen = 0;
int rc;
/*
* Read BHS
*/
if (rc != IDM_STATUS_SUCCESS) {
return (IDM_STATUS_FAIL);
}
/*
* Check actual AHS length against the amount available in the buffer
*/
"idm_sorecvhdr: exceeded the max data segment length");
return (IDM_STATUS_FAIL);
}
/* Allocate a new header segment and change the callback */
/*
* This callback will restore the expected values after
* the RX PDU has been processed.
*/
}
/*
* Setup receipt of additional header and header digest (if enabled).
*/
iovlen++;
}
iovlen++;
}
if ((iovlen != 0) &&
total_len) != 0)) {
return (IDM_STATUS_FAIL);
}
/*
* Validate header digest if enabled
*/
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
{
cr->cr_protocol);
return (IDM_STATUS_FAIL);
}
/* Bind the socket if configured to do so */
return (IDM_STATUS_FAIL);
}
}
if (idmrc != IDM_STATUS_SUCCESS) {
return (IDM_STATUS_FAIL);
}
/* Set up socket options */
return (IDM_STATUS_SUCCESS);
}
/*
* idm_so_ini_conn_destroy()
* Tear down the sockets transport connection resources.
*/
static void
{
}
/*
* 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
{
int rc;
/* Set to none block socket mode */
do {
CRED());
/* socket success or already success */
break;
}
(rc == ECONNRESET)) {
/* socket connection timeout or refuse */
break;
}
lbolt = ddi_get_lbolt();
if (lbolt > conn_login_max) {
/*
* Connection retry timeout,
* failed connect to target.
*/
break;
}
if (lbolt < conn_login_interval) {
/* TCP connect still in progress */
continue;
} else {
}
}
} while (rc != 0);
/* resume to nonblock mode */
if (rc == IDM_STATUS_SUCCESS) {
}
} else {
}
if (rc != 0) {
return (IDM_STATUS_FAIL);
}
return (IDM_STATUS_SUCCESS);
}
{
return (idmrc);
}
static void
{
}
/*
* 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
{
return (IDM_STATUS_SUCCESS);
}
static idm_status_t
{
ic->ic_transport_hdrlen = 0;
/* Set the scoreboarding flag on this connection */
/*
* Initialize tx thread mutex and list
*/
return (IDM_STATUS_SUCCESS);
}
static void
{
}
static void
{
struct sockaddr_in6 t_addr;
t_addrlen = sizeof (struct sockaddr_in6);
/* Set the local and remote addresses in the idm conn handle */
while (so_conn->ic_rx_thread_did == 0 ||
so_conn->ic_tx_thread_did == 0)
}
/*
* idm_so_conn_disconnect()
* Shutdown the socket connection and stop the thread
*/
static void
{
/* We need to wakeup the TX thread */
/* This should wakeup the RX thread if it is sleeping */
}
/*
* 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
{
/* Set the new sockets service in svc handle */
return (IDM_STATUS_SUCCESS);
}
/*
* idm_so_tgt_svc_destroy()
* Teardown sockets resources allocated in idm_so_tgt_svc_create()
*/
static void
{
/* the socket will have been torn down; free the service */
}
/*
* idm_so_tgt_svc_online()
* Launch a watch thread on the svc allocated in idm_so_tgt_svc_create()
*/
static idm_status_t
{
struct sockaddr_in6 sin6_ip;
/*
* Try creating an IPv6 socket first
*/
return (IDM_STATUS_FAIL);
} else {
/*
* Turn off SO_MAC_EXEMPT so future sobinds succeed
*/
return (IDM_STATUS_FAIL);
}
}
return (IDM_STATUS_FAIL);
}
/* Launch a watch thread */
/* Failure to launch; teardown the socket */
return (IDM_STATUS_FAIL);
}
/* Wait for the port watcher thread to start */
while (!so_svc->is_thread_running)
return (IDM_STATUS_SUCCESS);
}
/*
* idm_so_tgt_svc_offline
*
* Stop listening on the IP address and port identified by idm_svc_t.
*/
static void
{
/*
* Teardown socket
*/
/*
* Now we expect the port watcher thread to terminate
*/
}
/*
* Watch thread for target service connection establishment.
*/
void
idm_so_svc_port_watcher(void *arg)
{
idm_conn_t *ic;
int rc;
struct sockaddr_in6 t_addr;
t_addrlen = sizeof (struct sockaddr_in6);
while (so_svc->is_thread_running) {
if (rc == ECONNABORTED)
continue;
/* Connection problem */
break;
}
/*
* Turn off SO_MAC_EXEMPT so future sobinds succeed
*/
&ic);
if (idmrc != IDM_STATUS_SUCCESS) {
/* Drop connection */
continue;
}
if (idmrc != IDM_STATUS_SUCCESS) {
continue;
}
/*
* Kick the state machine. At CS_S3_XPT_UP the state machine
* will notify the client (target) about the new connection.
*/
}
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
{
/*
* There is nothing to cleanup on initiator connections
*/
return (IDM_STATUS_SUCCESS);
/*
* 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.
*/
if (idb->idb_in_transport) {
/*
* idm_buf_rx_from_ini_done releases idt->idt_mutex
*/
int, XFER_BUF_RX_FROM_INI);
}
}
/*
* 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.
*/
/*
* idm_buf_tx_to_ini_done releases idt->idt_mutex
*/
int, XFER_BUF_TX_TO_INI);
}
}
return (IDM_STATUS_SUCCESS);
}
/*
* idm_so_negotiate_key_values() validates the key values for this connection
*/
/* ARGSUSED */
static kv_status_t
{
/* 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 void
{
char *nvp_name;
int nvrc;
const idm_kv_xlate_t *ikvx;
case KI_HEADER_DIGEST:
case KI_DATA_DIGEST:
ASSERT(idm_status == 0);
/* Remove processed item from negotiated_nvl list */
break;
/*
* Just pass the value down to idm layer.
* No need to remove it from negotiated_nvl list here.
*/
break;
default:
break;
}
}
}
/*
* idm_so_declare_key_values() declares the key values for this connection
*/
/* ARGSUSED */
static kv_status_t
{
char *nvp_name;
int nvrc = 0;
const idm_kv_xlate_t *ikvx;
break;
}
if (outgoing_nvl &&
break;
}
break;
default:
break;
}
}
return (kvrc);
}
static idm_status_t
const idm_kv_xlate_t *ikvx)
{
int nvrc;
char *digest_choice_string;
case KI_HEADER_DIGEST:
break;
case KI_DATA_DIGEST:
break;
default:
ASSERT(0);
break;
}
case KI_HEADER_DIGEST:
break;
case KI_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
{
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
{
/*
* Look up the task corresponding to the initiator task tag
* to get the buffers affiliated with the task.
*/
return;
}
"idm_so_rx_datain: failed to find buffer");
return;
}
/*
* DataSN values should be sequential and should not have any gaps or
* repetitions. Check the DataSN with the one stored in the task.
*/
} else {
return;
}
/*
* PDUs in a sequence should be in continuously increasing
* address offset
*/
return;
}
/* Expected next relative buffer offset */
/*
* For now call scsi_rsp which will process the data rsp
* Revisit, need to provide an explicit client entry point for
* phase collapse completions.
*/
}
}
/*
* 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
{
/*
* Look up the task corresponding to the initiator task tag
* to get the buffers affiliated with the task.
*/
"idm_so_rx_dataout: failed to find task");
return;
}
"idm_so_rx_dataout: failed to find buffer");
return;
}
/* Keep track of data transferred - check data offsets */
return;
}
/* Expected next relative offset */
/*
* 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).
*/
/*
* 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.
*/
/*
* 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_buf_rx_from_ini_done releases idt->idt_mutex
*/
int, XFER_BUF_RX_FROM_INI);
return;
}
}
/*
* 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
{
return;
}
/* Find the buffer bound to the task by the iSCSI initiator */
return;
}
/* return buffer contains this data */
/* Overflow */
"buffer");
return;
}
idt->idt_exp_datasn = 0;
/*
* the idt_mutex is released in idm_so_send_rtt_data
*/
}
{
int pad_len;
int total_len;
pad_len = ((ISCSI_PAD_WORD_LEN -
(ISCSI_PAD_WORD_LEN - 1));
if (pad_len) {
pdu->isp_iovlen++;
}
/* setup data digest */
(char *)&data_digest_crc;
sizeof (data_digest_crc);
total_len += sizeof (data_digest_crc);
pdu->isp_iovlen++;
}
return (IDM_STATUS_IO);
}
pdu->isp_datalen);
if (pad_len) {
}
if (crc_calculated != data_digest_crc) {
"idm_sorecvdata: "
"CRC error: actual 0x%x, calc 0x%x",
/* 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.
*/
{
(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.
*/
"idm_sorecv_scsidata: could not find task");
return (IDM_STATUS_FAIL);
}
"buffer for offset %x opcode=%x",
return (IDM_STATUS_FAIL);
}
ASSERT(xfer_bytes != 0);
if (xfer_bytes != dlength) {
/*
* 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);
}
return (status);
}
static uint32_t
{
pdu->isp_iovlen++;
return (xfer_len);
}
int
{
/*
* 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.
*/
}
void
idm_sorx_thread(void *arg)
{
while (so_conn->ic_rx_thread_running) {
/*
* 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->isp_transport_hdrlen = 0;
/*
* Call idm_pdu_complete so that we call the callback
* and ensure any memory allocated in idm_sorecvhdr
* gets freed up.
*/
/*
* 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.
*/
if (so_conn->ic_rx_thread_running) {
}
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) {
/*
* 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.
*/
}
if (rc != 0) {
/*
* Call idm_pdu_complete so that we call the
* callback and ensure any memory allocated
* in idm_sorecvhdr gets freed up.
*/
/*
* 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.
*/
if (so_conn->ic_rx_thread_running) {
}
continue;
}
}
/*
* Process RX PDU
*/
}
/*
* 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) {
}
}
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
{
if (!so_conn->ic_tx_thread_running) {
return;
}
}
static idm_status_t
{
int pad_len;
uint32_t data_digest_crc = 0;
int total_len = 0;
int iovlen = 0;
/* Setup BHS */
iovlen++;
/* Setup header digest */
iovlen++;
}
/* Setup the data */
if (pdu->isp_datalen) {
/* Write of immediate data */
if (idt) {
/*
* If the initiator call to idm_buf_alloc
* failed then we can get to this point
* without a bound buffer. The associated
* connection failure will clean things up
* later. It would be nice to come up with
* a cleaner way to handle this. In
* particular it seems absurd to look up
* the task and the buffer just to update
* this counter.
*/
if (idb)
}
}
iovlen++;
}
/* Setup the data pad if necessary */
pad_len = ((ISCSI_PAD_WORD_LEN -
(ISCSI_PAD_WORD_LEN - 1));
if (pad_len) {
iovlen++;
}
/*
* Setup the data digest if enabled. Data-digest is not sent
* for login-phase PDUs.
*/
/*
* RFC3720/10.2.3: A zero-length Data Segment also
* implies a zero-length data digest.
*/
if (pdu->isp_datalen) {
pdu->isp_datalen);
}
if (pad_len) {
}
iovlen++;
}
/* Transmit the PDU */
total_len) != 0) {
/* Set error status */
"idm_so_tx: failed to transmit the PDU, so: %p ic: %p "
}
/*
* Success does not mean that the PDU actually reached the
* remote node since it could get dropped along the way.
*/
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.
* If the initiator supports phase collapse, the status bit must be set along
* with the F bit to indicate that the status is shipped together with the last
* Data-In PDU.
*
* 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
{
/*
* Put the idm_buf_t on the tx queue. It will be transmitted by
* idm_sotx_thread.
*/
if (!so_conn->ic_tx_thread_running) {
/*
* Don't release idt->idt_mutex since we're supposed to hold
* in when calling idm_buf_tx_to_ini_done
*/
int, XFER_BUF_TX_TO_INI);
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.
*/
/*
* 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
{
/* iSCSI layer fills the TTT, ITT, ExpCmdSN, MaxCmdSN */
/* set the rttsn, rtt.flags, rtt.data_offset and rtt.data_length */
/* Keep track of buffer offsets */
/*
* Transmit the PDU.
*/
return (IDM_STATUS_SUCCESS);
}
static idm_status_t
{
} else {
}
"idm_so_buf_alloc: failed buffer allocation");
return (IDM_STATUS_FAIL);
}
return (IDM_STATUS_SUCCESS);
}
/* ARGSUSED */
static idm_status_t
{
/* Ensure bufalloc'd flag is unset */
return (IDM_STATUS_SUCCESS);
}
/* ARGSUSED */
static void
{
/* nothing to do here */
}
static void
{
} else {
}
}
static void
{
/*
* Allocate a buffer to represent the RTT transfer. We could further
* optimize this by allocating the buffers internally from an rtt
* specific buffer cache since this is socket-specific code but for
* now we will keep it simple.
*/
/*
* If we're in FFP then the failure was likely a resource
* allocation issue and we should close the connection by
* sending a CE_TRANSPORT_FAIL event.
*
* If we're not in FFP then idm_buf_alloc will always
* fail and the state is transitioning to "complete" anyway
* so we won't bother to send an event.
*/
return;
}
/*
* The new buffer (if any) represents an additional
* reference on the task
*/
/*
* Put the idm_buf_t on the tx queue. It will be transmitted by
* idm_sotx_thread.
*/
if (!so_conn->ic_tx_thread_running) {
return;
}
/*
* 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.
*/
}
static void
{
/*
* Don't worry about status -- we assume any error handling
* is performed by the caller (idm_sotx_thread).
*/
}
static idm_status_t
{
idm_conn_t *ic;
while (remainder) {
return (IDM_STATUS_ABORTED);
}
/* check to see if we need to chunk the data */
if (remainder > max_dataseglen) {
} else {
}
/* Data PDU headers will always be sizeof (iscsi_hdr_t) */
/*
* 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.
*/
sizeof (iscsi_hdr_t));
/*
* Set DataSN, data offset, and flags in BHS
* For the prototype build, A = 0, S = 0, U = 0
*/
/* setup data */
/* Piggyback the status with the last data PDU */
}
}
data_offset += chunk;
/* Instrument the data-send DTrace probe. */
}
/*
* 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.
*/
/*
* Transmit the PDU. Call the internal routine directly
* as there is already implicit ordering.
*/
return (tx_status);
}
}
return (IDM_STATUS_SUCCESS);
}
/*
* TX PDU cache
*/
/* ARGSUSED */
int
{
return (0);
}
/* ARGSUSED */
void
{
/* reset values between use */
pdu->isp_datalen = 0;
}
/*
* RX PDU cache
*/
/* ARGSUSED */
int
{
return (0);
}
/* ARGSUSED */
static void
{
pdu->isp_iovlen = 0;
pdu->isp_sorx_buf = 0;
}
static void
{
/*
* We had to modify our cached RX PDU with a longer header buffer
* the fields back to what we would expect for a cached RX PDU.
*/
}
}
pdu->isp_datalen = 0;
pdu->isp_sorx_buf = 0;
}
/*
* This thread is only active when I/O is queued for transmit
* because the socket is busy.
*/
void
idm_sotx_thread(void *arg)
{
while (so_conn->ic_tx_thread_running) {
if (!so_conn->ic_tx_thread_running) {
goto tx_bail;
}
}
switch (object->idm_tx_obj_magic) {
case IDM_PDU_MAGIC: {
/* No IDM task */
}
break;
}
case IDM_BUF_MAGIC: {
/*
* TX thread owns the buffer so we expect it to
* be "in transport"
*/
if (IDM_CONN_ISTGT(ic)) {
/*
* idm_buf_tx_to_ini_done releases
* idt->idt_mutex
*/
int, XFER_BUF_TX_TO_INI);
} else {
}
break;
}
default:
}
if (status != IDM_STATUS_SUCCESS) {
}
}
/*
* Before we leave, we need to abort every item remaining in the
* TX list.
*/
switch (object->idm_tx_obj_magic) {
case IDM_PDU_MAGIC:
break;
case IDM_BUF_MAGIC: {
/*
* TX thread owns the buffer so we expect it to
* be "in transport"
*/
if (IDM_CONN_ISTGT(ic)) {
/*
* idm_buf_tx_to_ini_done releases
* idt->idt_mutex
*/
int, XFER_BUF_TX_TO_INI);
} else {
}
break;
}
default:
"idm_sotx_thread: Unexpected magic "
}
}
thread_exit();
/*NOTREACHED*/
}
static void
{
}
static void
{
}
/*
* Called by kernel sockets when the connection has been accepted or
* rejected. In early volo, a "disconnect" callback was sent instead of
* "connectfailed", so we check for both.
*/
/* ARGSUSED */
void
{
ev == KSOCKET_EV_CONNECTFAILED ||
if (ev == KSOCKET_EV_CONNECTED) {
itp->it_socket_error_code = 0;
} else {
/* Make sure the error code is non-zero on error */
if (info == 0)
info = ECONNRESET;
}
}
int
{
/*
* Set to non-block socket mode, with callback on connect
* Early volo used "disconnected" instead of "connectfailed",
* so set callback to look for both.
*/
if (rc != 0)
return (rc);
/* Set to non-blocking mode */
nonblocking = 1;
CRED());
if (rc != 0)
goto cleanup;
for (;;) {
/*
* Warning -- in a loopback scenario, the call to
* the connect_cb can occur inside the call to
* ksocket_connect. Do not hold the mutex around the
* call to ksocket_connect.
*/
/* socket success or already success */
rc = 0;
break;
}
break;
}
/* TCP connect still in progress. See if out of time. */
if (ddi_get_lbolt() > conn_login_max) {
/*
* Connection retry timeout,
* failed connect to target.
*/
break;
}
/*
* TCP connect still in progress. Sleep until callback.
* Do NOT go to sleep if the callback already occurred!
*/
if (!it.it_callback_called) {
}
if (it.it_callback_called) {
break;
}
/* If timer expires, go call ksocket_connect one last time. */
}
/* resume blocking mode */
nonblocking = 0;
CRED());
if (rc != 0) {
}
return (rc);
}
void
{
int dp_addr_size;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
/* Build sockaddr_storage for this portal (idm_addr_t) */
if (dp_addr_size == sizeof (struct in_addr)) {
/* IPv4 */
} else if (dp_addr_size == sizeof (struct in6_addr)) {
/* IPv6 */
} else {
ASSERT(0);
}
}
/*
* return a human-readable form of a sockaddr_storage, in the form
* [ip-address]:port. This is used in calls to logging functions.
* If several calls to idm_sa_ntop are made within the same invocation
* of a logging function, then each one needs its own buf.
*/
const char *
{
static const char bogus_ip[] = "[0].-1";
char tmp[INET6_ADDRSTRLEN];
case AF_INET6:
{
const struct sockaddr_in6 *in6 =
(const struct sockaddr_in6 *) sa;
goto err;
}
goto err;
}
/* struct sockaddr_storage gets port info from v4 loc */
return (buf);
}
case AF_INET:
{
const struct sockaddr_in *in =
(const struct sockaddr_in *) sa;
goto err;
}
goto err;
}
return (buf);
}
default:
break;
}
err:
return (buf);
}