summaryrefslogtreecommitdiffstats
path: root/src/server
diff options
context:
space:
mode:
authorSebastian Vater2025-08-07 17:13:16 +0200
committerSebastian Vater2025-08-07 17:13:16 +0200
commitc1be00f5adc0c98028456b11d4e9331f5b8476c0 (patch)
treef12c00e6f800e6ed404d987a5290c2c46c9bc640 /src/server
parentRemoved static iSCSI functions from header file and added CRC32C lookup table... (diff)
downloaddnbd3-c1be00f5adc0c98028456b11d4e9331f5b8476c0.tar.gz
dnbd3-c1be00f5adc0c98028456b11d4e9331f5b8476c0.tar.xz
dnbd3-c1be00f5adc0c98028456b11d4e9331f5b8476c0.zip
Implemented main loop of handling incoming iSCSI header packet data. Also added a lot of new iSCSI structures (globals, portal groups, portals, ports, etc.). Finally, fixed some bugs.
Diffstat (limited to 'src/server')
-rw-r--r--src/server/iscsi.c1611
-rw-r--r--src/server/iscsi.h648
2 files changed, 2185 insertions, 74 deletions
diff --git a/src/server/iscsi.c b/src/server/iscsi.c
index 49e0f46..4d1ab90 100644
--- a/src/server/iscsi.c
+++ b/src/server/iscsi.c
@@ -24,6 +24,7 @@
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
+#include <sys/socket.h>
#include <dnbd3/shared/log.h>
#include "iscsi.h"
@@ -873,7 +874,7 @@ iscsi_bhs_packet *iscsi_append_ahs_packet(iscsi_bhs_packet *packet_data, const u
const uint32_t new_pkt_size = old_pkt_size + iscsi_align(ahs_len, ISCSI_ALIGN_SIZE);
if ( new_pkt_size > (sizeof(struct iscsi_bhs_packet) + 1020UL) ) {
- logadd( LOG_ERROR, "iscsi_append_ahs_packet: Total numer of AHS packets exceeds 255 DWORDs" );
+ logadd( LOG_ERROR, "iscsi_append_ahs_packet: Total numer of AHS packet size exceeds 255 DWORDs" );
return NULL;
}
@@ -968,6 +969,54 @@ iscsi_ahs_packet *iscsi_get_ahs_packet(const iscsi_bhs_packet *packet_data, cons
}
/**
+ * @brief Allocate and initialize an iSCSI header digest (CRC32C) and appends it to existing data stream.
+ *
+ * Constructs and appends an header digest (CRC32C) to already allocated
+ * packet data. There is no guarantee that the pointer stays the same.
+ * Any references to the old structure need to be updated!\n
+ * This function currently throws away any data beyond AHS.
+ *
+ * @param[in] packet_data Pointer to packet data to append to. If NULL, a Basic
+ * Header Segment (BHS) will be created and initialized before adding the
+ * header digest.
+ * @param[in] header_digest_size Length of header digest. Currently, only
+ * 0, in which case the header digest will be removed, or 4 for CRC32C
+ * are allowed.
+ * @return New pointer to BHS structure with additional header digest attached
+ * or NULL in case of an reallocation error or header digest is neither 0 nor 4.
+ */
+iscsi_bhs_packet *iscsi_append_header_digest_packet(iscsi_bhs_packet *packet_data, const int header_digest_size)
+{
+ if ( packet_data == NULL ) {
+ packet_data = iscsi_create_packet();
+
+ if ( packet_data == NULL )
+ return packet_data;
+ }
+
+ if ( (header_digest_size != 0) || (header_digest_size != ISCSI_DIGEST_SIZE) ) {
+ logadd( LOG_ERROR, "iscsi_append_header_digest_packet: Header digest size MUST be either 0 or 4 bytes" );
+
+ return NULL;
+ }
+
+ const uint32_t old_pkt_size = (const uint32_t) sizeof(struct iscsi_bhs_packet) + (packet_data->total_ahs_len << 2UL);
+ const uint32_t new_pkt_size = old_pkt_size + header_digest_size;
+
+ packet_data = (iscsi_bhs_packet *) realloc( packet_data, new_pkt_size );
+
+ if ( packet_data == NULL ) {
+ logadd( LOG_ERROR, "iscsi_append_header_digest_packet: Out of memory while allocating iSCSI header digest packet data for appending" );
+
+ return packet_data;
+ }
+
+ memset( (((uint8_t *) packet_data) + old_pkt_size), 0, header_digest_size );
+
+ return packet_data;
+}
+
+/**
* @brief Allocate and initialize an iSCSI DS packet and append to existing data stream.
*
* Constructs and appends DataSegment (DS) to already allocated packet data.\n
@@ -1011,7 +1060,7 @@ iscsi_bhs_packet *iscsi_append_ds_packet(iscsi_bhs_packet *packet_data, const in
return packet_data;
}
- iscsi_put_be24( packet_data->ds_len, ds_len );
+ iscsi_put_be24( &packet_data->ds_len, ds_len );
memset( ((uint8_t *) packet_data) + old_pkt_size, 0, (new_pkt_size - old_pkt_size) );
return packet_data;
@@ -1319,8 +1368,8 @@ int iscsi_validate_packet(const struct iscsi_bhs_packet *packet_data, const uint
if ( (ISCSI_VERSION_MIN < login_req_pkt->version_min) || (ISCSI_VERSION_MAX > login_req_pkt->version_max) )
return ISCSI_VALIDATE_PACKET_RESULT_ERROR_UNSUPPORTED_VERSION;
- if ( ((login_req_pkt->flags < 0) && ((login_req_pkt->flags & ISCSI_LOGIN_REQ_FLAGS_CONTINUE) != 0)) || (login_req_pkt->flags & ~(ISCSI_LOGIN_REQ_FLAGS_CONTINUE | ISCSI_LOGIN_REQ_FLAGS_TRANSMIT) != 0) || (login_req_pkt->reserved != 0) || (login_req_pkt->reserved2[0] != 0) || (login_req_pkt->reserved2[1] != 0) )
- return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // If C bit is set, T MUST be cleared and reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data
+ if ( (ISCSI_LOGIN_REQ_FLAGS_GET_CURRENT_STAGE(login_req_pkt->flags) == ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_RESERVED) || ((ISCSI_LOGIN_REQ_FLAGS_GET_NEXT_STAGE(login_req_pkt->flags) == ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_RESERVED) && ((login_req_pkt->flags & ISCSI_LOGIN_REQ_FLAGS_TRANSMIT) != 0)) || ((login_req_pkt->flags < 0) && ((login_req_pkt->flags & ISCSI_LOGIN_REQ_FLAGS_CONTINUE) != 0)) || (login_req_pkt->flags & ~(ISCSI_LOGIN_REQ_FLAGS_CONTINUE | ISCSI_LOGIN_REQ_FLAGS_TRANSMIT) != 0) || (login_req_pkt->reserved != 0) || (login_req_pkt->reserved2[0] != 0) || (login_req_pkt->reserved2[1] != 0) )
+ return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Current Stage (CSG) is set to reserved and Next Stage (NSG) is reserved and T bit is set, if C bit is set, T MUST be cleared and reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data
return iscsi_validate_key_value_pairs( ((const uint8_t *) login_req_pkt) + iscsi_align(ds_len, ISCSI_ALIGN_SIZE), ds_len );
@@ -1419,8 +1468,8 @@ int iscsi_validate_packet(const struct iscsi_bhs_packet *packet_data, const uint
if ( (ISCSI_VERSION_MIN < login_response_pkt->version_max) || (ISCSI_VERSION_MAX > login_response_pkt->version_max) || (ISCSI_VERSION_MIN < login_response_pkt->version_active) || (ISCSI_VERSION_MAX > login_response_pkt->version_active) )
return ISCSI_VALIDATE_PACKET_RESULT_ERROR_UNSUPPORTED_VERSION;
- if ( ((login_response_pkt->flags < 0) && ((login_response_pkt->flags & ISCSI_LOGIN_RESPONSE_FLAGS_CONTINUE) != 0)) || (login_response_pkt->flags & ~(ISCSI_LOGIN_RESPONSE_FLAGS_CONTINUE | ISCSI_LOGIN_RESPONSE_FLAGS_TRANSMIT) != 0) || (login_response_pkt->reserved != 0) || (login_response_pkt->reserved2 != 0) || (login_response_pkt->reserved3 != 0) )
- return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // If C bit is set, T MUST be cleared and reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data
+ if ( (ISCSI_LOGIN_RESPONSES_FLAGS_GET_CURRENT_STAGE(login_response_pkt->flags) == ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_RESERVED) || ((ISCSI_LOGIN_RESPONSES_FLAGS_GET_NEXT_STAGE(login_response_pkt->flags) == ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_RESERVED) && ((login_response_pkt->flags & ISCSI_LOGIN_RESPONSE_FLAGS_TRANSMIT) != 0)) || ((login_response_pkt->flags < 0) && ((login_response_pkt->flags & ISCSI_LOGIN_RESPONSE_FLAGS_CONTINUE) != 0)) || (login_response_pkt->flags & ~(ISCSI_LOGIN_RESPONSE_FLAGS_CONTINUE | ISCSI_LOGIN_RESPONSE_FLAGS_TRANSMIT) != 0) || (login_response_pkt->reserved != 0) || (login_response_pkt->reserved2 != 0) || (login_response_pkt->reserved3 != 0) )
+ return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Current Stage (CSG) is set to reserved and Next Stage (NSG) is reserved and T bit is set, if C bit is set, T MUST be cleared and reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data
return iscsi_validate_key_value_pairs( ((const uint8_t *) login_response_pkt) + iscsi_align(ds_len, ISCSI_ALIGN_SIZE), ds_len );
@@ -1583,7 +1632,7 @@ static int iscsi_parse_text_key_value_pair(iscsi_hashmap *pairs, const uint8_t *
return -1L;
}
- const uint8_t *hash_val = (uint8_t *) malloc( val_len + 1UL );
+ uint8_t *hash_val = (uint8_t *) malloc( val_len + 1UL );
if ( hash_val == NULL ) {
logadd( LOG_ERROR, "iscsi_parse_text_key_value_pair: Out of memory allocating memory for value string" );
@@ -1633,7 +1682,7 @@ int iscsi_parse_key_value_pairs(iscsi_hashmap *pairs, const uint8_t *packet_data
for (key_val_pair_len = 0; (key_val_pair_len < len) && packet_data[key_val_pair_len] != '\0'; key_val_pair_len++) {
}
- const uint8_t *tmp_partial_buf = iscsi_sprintf_alloc( "%s%s", *partial_pairs, (const char *) packet_data );
+ uint8_t *tmp_partial_buf = iscsi_sprintf_alloc( "%s%s", *partial_pairs, (const char *) packet_data );
if ( tmp_partial_buf == NULL )
return -1L;
@@ -1809,18 +1858,531 @@ iscsi_key_value_pair_packet *iscsi_create_key_value_pairs_packet(const iscsi_has
}
/**
- * @brief Creates data structure for an iSCSI connection request.
+ * @brief Allocates and adds an integer value to a key / value hash map pair.
+ *
+ * This function allocates memory for a string key
+ * and its integer representation as string value.
+ *
+ * @param[in] key_value_pairs Pointer to the hash map which should
+ * contain the added integer key and value pair. NULL is NOT
+ * allowed here, so be careful.
+ * @param[in] key String containing the key name as string. May
+ * NOT be NULL, so take caution.
+ * @param[in] value Integer containing the value to be stored.
+ * @return 0 on successful operation, or a negative value on
+ * error (memory exhaustion).
+ */
+static int iscsi_add_int_key_value_pair(iscsi_hashmap *key_value_pairs, const uint8_t *key, const int value)
+{
+ const uint key_len = strlen( key ) + 1;
+ uint8_t *hash_key = iscsi_hashmap_key_create( key, key_len );
+
+ if ( hash_key == NULL ) {
+ logadd( LOG_ERROR, "iscsi_add_int_key_value_pair: Out of memory allocating key." );
+
+ return -1L;
+ }
+
+ uint8_t *hash_val = iscsi_sprintf_alloc( "%d", value );
+
+ if ( hash_val == NULL ) {
+ logadd( LOG_ERROR, "iscsi_add_int_key_value_pair: Out of memory allocating integer value." );
+
+ return -1L;
+ }
+
+ return iscsi_hashmap_put( key_value_pairs, hash_key, key_len, hash_val );
+}
+
+/**
+ * @brief Allocates and adds an boolean value to a key / value hash map pair.
+ *
+ * This function allocates memory for a string key
+ * and its integer value.\n
+ * The string representation for true is: Yes\n
+ * The string representation for false is: No
+ *
+ * @param[in] key_value_pairs Pointer to the hash map which should
+ * contain the added boolean key and value pair. NULL is NOT
+ * allowed here, so be careful.
+ * @param[in] key String containing the key name as string. May
+ * NOT be NULL, so take caution.
+ * @param[in] value Boolean containing the value to be stored
+ * as string.
+ * @return 0 on successful operation, or a negative value on
+ * error (memory exhaustion).
+ */
+static int iscsi_add_bool_key_value_pair(iscsi_hashmap *key_value_pairs, const uint8_t *key, const int value)
+{
+ const uint key_len = strlen( key ) + 1;
+ uint8_t *hash_key = iscsi_hashmap_key_create( key, key_len );
+
+ if ( hash_key == NULL ) {
+ logadd( LOG_ERROR, "iscsi_add_bool_key_value_pair: Out of memory allocating key" );
+
+ return -1L;
+ }
+
+ uint8_t *bool_val = (value ? "Yes" : "No");
+ uint8_t *hash_val = (uint8_t *) malloc( strlen( bool_val ) + 1 );
+
+ if ( hash_val == NULL ) {
+ logadd( LOG_ERROR, "iscsi_add_bool_key_value_pair: Out of memory allocating boolean string value" );
+
+ return -1L;
+ }
+
+ strcpy( hash_val, bool_val );
+
+ return iscsi_hashmap_put( key_value_pairs, hash_key, key_len, hash_val );
+}
+
+/**
+ * @brief Creates and initializes an iSCSI portal group.
+ *
+ * Specified tag and flags are used for portal group
+ * initialization.
+ * @param[in] tag Tag to associate with the portal group.
+ * @param[in] flags Flags to set for the portal group.
+ * @return Pointer to allocated and initialized portal group
+ * or NULL in case of memory
+ */
+iscsi_portal_group *iscsi_portal_group_create(const int tag, const int flags)
+{
+ iscsi_portal_group *portal_group = (iscsi_portal_group *) malloc( sizeof(struct iscsi_portal_group) );
+
+ if ( portal_group == NULL ) {
+ logadd( LOG_ERROR, "iscsi_portal_group_create: Out of memory allocating iSCSI portal group structure" );
+
+ return NULL;
+ }
+
+ portal_group->portals = iscsi_hashmap_create( 0UL );
+
+ if ( portal_group->portals == NULL ) {
+ logadd( LOG_ERROR, "iscsi_portal_group_create: Out of memory allocating iSCSI portal hash map" );
+
+ free( portal_group );
+
+ return NULL;
+ }
+
+ portal_group->ref_count = 0L;
+ portal_group->tag = tag;
+ portal_group->flags = flags;
+ portal_group->chap_group = 0L;
+
+ return portal_group;
+}
+
+/**
+ * @brief iSCSI portal destructor callback for hash map.
+ *
+ * Callback function for deallocation of an iSCSI
+ * portal stored in the iSCSI portal group hash map.
+ *
+ * @param[in] key Pointer to zero padded key. NULL is
+ * an invalid pointer here, so be careful.
+ * @param[in] key_size Number of bytes for the key, MUST
+ * be a multiple of 8 bytes which is NOT checked, so
+ * be careful.
+ * @param[in] value Value of the key, NULL is allowed.
+ * @param[in,out] user_data This argument is not used by
+ * this function and should be always NULL for now, as
+ * there is a possibility for future usage.
+ * @return Always returns 0 as this function cannot fail.
+ */
+int iscsi_portal_destroy_callback(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data)
+{
+ iscsi_portal_destroy( (iscsi_portal *) value );
+ iscsi_hashmap_key_destroy( key );
+
+ return 0L;
+}
+
+/**
+ * @brief Deallocates resources acquired by iscsi_portal_group_create.
+ *
+ * This function frees the associated hash map containing the
+ * poptals and the structure itself.
+ * @param[in] portal_group Pointer to iSCSI portal group to deallocate.
+ * May be NULL in which case this function does nothing.
+ */
+void iscsi_portal_group_destroy(iscsi_portal_group *portal_group)
+{
+ if ( portal_group != NULL ) {
+ if ( portal_group->portals != NULL ) {
+ iscsi_hashmap_iterate( portal_group->portals, iscsi_portal_destroy_callback, NULL );
+ iscsi_hashmap_destroy( portal_group->portals );
+
+ portal_group->portals = NULL;
+ }
+
+ free( portal_group );
+ }
+}
+
+/**
+ * @brief Adds an iSCSI portal to the iSCSI portal group hash map.
+ *
+ * This function allocates host:port of iSCSI portal for use
+ * as key and sets the portal group in the portal.
+ *
+ * @param[in] iSCSI portal group to add portal to. May NOT be NULL,
+ * so take caution.
+ * @param[in] iSCSI portal to add to portal group. NULL is NOT
+ * allowed here, so be careful.
+ * @retval -1 An error occured during adding the portal,
+ * usually caused by memory exhaustion
+ * @retval 0 The portal has been added successfully to the
+ * portal group.
+ */
+int iscsi_portal_group_add_portal(iscsi_portal_group *portal_group, iscsi_portal *portal)
+{
+ uint8_t *tmp_buf = iscsi_sprintf_alloc( "%s:%s", portal->host, portal->port );
+
+ if ( tmp_buf == NULL )
+ return -1L;
+
+ const uint key_len = strlen( tmp_buf ) + 1;
+ uint8_t *key = iscsi_hashmap_key_create( tmp_buf, key_len );
+
+ free( tmp_buf );
+
+ if ( key == NULL ) {
+ logadd( LOG_ERROR, "iscsi_portal_group_add_portal: Out of memory allocating key for iSCSI portal" );
+
+ return -1L;
+ }
+
+ int rc = iscsi_hashmap_put( portal_group->portals, key, key_len, (iscsi_portal *) portal );
+
+ if ( rc < 0 ) {
+ logadd( LOG_ERROR, "iscsi_portal_group_add_portal: Adding portal to hash map containing iSCSI portal group failed" );
+
+ iscsi_hashmap_key_destroy( key );
+
+ return rc;
+ }
+
+ portal->group = portal_group;
+
+ return 0L;
+}
+
+/**
+ * @brief Allocates and initializes an iSCSI portal structure.
+ *
+ * This function makes a copy of the passed host / IP address
+ * and port, but does NOT initialize the socket.
+ *
+ * @param[in] host Host / IP address of the portal.
+ * @param[in] port Port of the portal.
+ * @return Pointer to iSCSI portal structure or NULL
+ * in case of an error (memory exhausted).
+ */
+iscsi_portal *iscsi_portal_create(const uint8_t *host, const uint8_t *port)
+{
+ iscsi_portal *portal = (iscsi_portal *) malloc( sizeof(struct iscsi_portal) );
+
+ if ( portal == NULL ) {
+ logadd( LOG_ERROR, "iscsi_portal_create: Out of memory allocating iSCSI portal structure" );
+
+ return NULL;
+ }
+
+ portal->group = NULL;
+
+ const uint host_len = strlen( host ) + 1;
+
+ portal->host = (uint8_t *) malloc( host_len );
+
+ if ( portal->host == NULL ) {
+ logadd( LOG_ERROR, "iscsi_portal_create: Out of memory allocating iSCSI portal host name" );
+
+ return NULL;
+ }
+
+ memcpy( portal->host, host, host_len );
+
+ const uint port_len = strlen( port ) + 1;
+
+ portal->port = (uint8_t *) malloc( port_len );
+
+ if ( portal->port == NULL ) {
+ logadd( LOG_ERROR, "iscsi_portal_create: Out of memory allocating iSCSI portal port" );
+
+ return NULL;
+ }
+
+ memcpy( portal->port, port, port_len );
+
+ portal->sock = 0L;
+
+ return portal;
+}
+
+/**
+ * @brief Deallocates all resources acquired by iscsi_portal_create.
+ *
+ * This function frees the memory acquired for host / IP address
+ * and port but does NOT remove it from the portal group hash map.
+ *
+ * @param[in] portal Pointer to iSCSI portal to be deallocated,
+ * which may be NULL in which case the function does nothing.
+ */
+void iscsi_portal_destroy(iscsi_portal *portal)
+{
+ if ( portal != NULL ) {
+ if ( portal->port != NULL ) {
+ free( portal->port );
+
+ portal->port = NULL;
+ }
+
+ if ( portal->host != NULL ) {
+ free( portal->host );
+
+ portal->host = NULL;
+ }
+
+ free( portal );
+ }
+}
+
+/**
+ * @brief Allocates and initializes an iSCSI port.
+ *
+ * THis function marks the port in use, but does
+ * NOT set a transport ID. Everything else is
+ * initialized, however.
+ *
+ * @param[in] name Pointer to port name. This
+ * may NOT be NULL, so be careful.
+ * @param[in] id Identifier for this port.
+ * @param[in] index Index number for this port.
+ * @return Pointer to initialized iSCSI port or NULL
+ * in case of memory exhaustion.
+ */
+iscsi_port *iscsi_port_create(const uint8_t *name, const uint64_t id, const uint16_t index)
+{
+ iscsi_port *port = (iscsi_port *) malloc( sizeof(struct iscsi_port) );
+
+ if ( port == NULL ) {
+ logadd( LOG_ERROR, "iscsi_port_create: Out of memory allocating iSCSI port" );
+
+ return NULL;
+ }
+
+ const uint name_len = strlen( name ) + 1;
+
+ port->name = (uint8_t *) malloc( name_len );
+
+ if ( port->name == NULL ) {
+ logadd( LOG_ERROR, "iscsi_port_create: Out of memory allocating iSCSI port name" );
+
+ free( port );
+
+ return NULL;
+ }
+
+ memcpy( port->name, name, name_len );
+
+ port->transport_id = NULL;
+ port->id = id;
+ port->index = index;
+ port->flags = ISCSI_PORT_FLAGS_IN_USE;
+ port->transport_id_len = 0U;
+
+ return port;
+}
+
+/**
+ * @brief Deallocates all resources acquired iscsi_port_create.
+ *
+ * This function also frees the port name and transport ID,
+ * if they exist.
+ *
+ * @param[in] port iSCSI port to deallocate. This may
+ * be NULL in which case nothing happens.
+ */
+void iscsi_port_destroy(iscsi_port *port)
+{
+ if ( port != NULL ) {
+ if ( port->name != NULL ) {
+ free( port->name );
+
+ port->name = NULL;
+ }
+
+ if ( port->transport_id != NULL ) {
+ free( port->transport_id );
+
+ port->transport_id = NULL;
+ }
+
+ free( port );
+ }
+}
+
+/**
+ * @brief Creates and initializes an iSCSI session.
+ *
+ * This function creates and initializes all relevant
+ * data structures of an ISCSI session.\n
+ * Default key and value pairs are created and
+ * assigned before they are negotiated at the
+ * login phase.
+ *
+ * @param[in] conn Pointer to iSCSI connection to associate with the session.
+ * @param[in] target Pointer to iSCSI target node to assign with the session.
+ * @param[in] type Session type to initialize the session with.
+ * @return Pointer to initialized iSCSI session or NULL in case an error
+ * occured (usually due to memory exhaustion).
+ */
+iscsi_session *iscsi_session_create(iscsi_connection *conn, const iscsi_target_node *target, const int type)
+{
+ iscsi_session *session = (iscsi_session *) malloc( sizeof(struct iscsi_session) );
+
+ if ( session == NULL ) {
+ logadd( LOG_ERROR, "iscsi_session_create: Out of memory allocating iSCSI session" );
+
+ return NULL;
+ }
+
+ session->max_conns = ISCSI_SESSION_DEFAULT_MAX_CONNECTIONS;
+ session->max_outstanding_r2t = ISCSI_SESSION_DEFAULT_MAX_OUTSTANDING_R2T;
+ session->default_time_to_wait = ISCSI_SESSION_DEFAULT_TIME_TO_WAIT;
+ session->default_time_to_retain = ISCSI_SESSION_DEFAULT_TIME_TO_RETAIN;
+ session->first_burst_len = ISCSI_SESSION_DEFAULT_FIRST_BURST_LEN;
+ session->max_burst_len = ISCSI_SESSION_DEFAULT_MAX_BURST_LEN;
+ session->init_r2t = ISCSI_SESSION_DEFAULT_INIT_R2T;
+ session->immediate_data = ISCSI_SESSION_DEFAULT_IMMEDIATE_DATA;
+ session->data_pdu_in_order = ISCSI_SESSION_DEFAULT_DATA_PDU_IN_ORDER;
+ session->data_seq_in_order = ISCSI_SESSION_DEFAULT_DATA_SEQ_IN_ORDER;
+ session->err_recovery_level = ISCSI_SESSION_DEFAULT_ERR_RECOVERY_LEVEL;
+ session->tag = conn->pg_tag;
+
+ session->connections = iscsi_hashmap_create( session->max_conns );
+
+ if ( session->connections == NULL ) {
+ logadd( LOG_ERROR, "iscsi_session_create: Out of memory allocating iSCSI session connection hash map" );
+
+ free( session );
+
+ return NULL;
+ }
+
+ uint8_t *conn_key = iscsi_hashmap_key_create( (uint8_t *) &conn->cid, sizeof(conn->cid) );
+
+ if ( conn_key == NULL ) {
+ logadd( LOG_ERROR, "iscsi_session_create: Out of memory allocating iSCSI session connection hash map" );
+
+ iscsi_hashmap_destroy( session->connections );
+ free( session );
+
+ return NULL;
+ }
+
+ iscsi_hashmap_put( session->connections, conn_key, sizeof(conn->cid), (iscsi_connection *) conn );
+
+ session->target = target;
+ session->isid = 0LL;
+ session->type = type;
+ session->current_text_init_task_tag = 0xFFFFFFFFUL;
+
+ session->key_value_pairs = iscsi_hashmap_create( 0UL );
+
+ if ( session->key_value_pairs == NULL ) {
+ logadd( LOG_ERROR, "iscsi_session_create: Out of memory allocating iSCSI session key and value pairs hash map" );
+
+ iscsi_hashmap_key_destroy( conn_key );
+ iscsi_hashmap_destroy( session->connections );
+ free( session );
+
+ return NULL;
+ }
+
+ int rc = iscsi_add_int_key_value_pair( session->key_value_pairs, (uint8_t *) ISCSI_LOGIN_AUTH_SESSION_TEXT_KEY_MAX_CONNECTIONS, session->max_conns );
+ rc |= iscsi_add_int_key_value_pair( session->key_value_pairs, (uint8_t *) ISCSI_LOGIN_AUTH_SESSION_TEXT_KEY_MAX_OUTSTANDING_R2T, session->max_outstanding_r2t );
+ rc |= iscsi_add_int_key_value_pair( session->key_value_pairs, (uint8_t *) ISCSI_LOGIN_AUTH_SESSION_TEXT_KEY_DEFAULT_TIME_WAIT, session->default_time_to_wait );
+ rc |= iscsi_add_int_key_value_pair( session->key_value_pairs, (uint8_t *) ISCSI_LOGIN_AUTH_SESSION_TEXT_KEY_DEFAULT_TIME_RETAIN, session->default_time_to_retain );
+ rc |= iscsi_add_int_key_value_pair( session->key_value_pairs, (uint8_t *) ISCSI_LOGIN_AUTH_SESSION_TEXT_KEY_FIRST_BURST_LEN, session->first_burst_len );
+ rc |= iscsi_add_int_key_value_pair( session->key_value_pairs, (uint8_t *) ISCSI_LOGIN_AUTH_SESSION_TEXT_KEY_MAX_BURST_LEN, session->max_burst_len );
+ rc |= iscsi_add_bool_key_value_pair( session->key_value_pairs, (uint8_t *) ISCSI_LOGIN_AUTH_SESSION_TEXT_KEY_IMMEDIATE_DATA, session->immediate_data );
+ rc |= iscsi_add_bool_key_value_pair( session->key_value_pairs, (uint8_t *) ISCSI_LOGIN_AUTH_SESSION_TEXT_KEY_DATA_PDU_IN_ORDER, session->data_pdu_in_order );
+ rc |= iscsi_add_bool_key_value_pair( session->key_value_pairs, (uint8_t *) ISCSI_LOGIN_AUTH_SESSION_TEXT_KEY_DATA_SEQ_IN_ORDER, session->data_seq_in_order );
+ rc |= iscsi_add_int_key_value_pair( session->key_value_pairs, (uint8_t *) ISCSI_LOGIN_AUTH_SESSION_TEXT_KEY_ERR_RECOVERY_LEVEL, session->err_recovery_level );
+ rc |= iscsi_add_int_key_value_pair( conn->key_value_pairs, (uint8_t *) ISCSI_LOGIN_AUTH_SESSION_TEXT_KEY_MAX_RECV_DS_LEN, conn->max_recv_ds_len );
+
+ if ( rc != 0 ) {
+ logadd( LOG_ERROR, "iscsi_session_create: Out of memory adding iSCSI session key and integer value pair" );
+
+ iscsi_hashmap_iterate( session->key_value_pairs, iscsi_hashmap_key_destroy_value_callback, NULL );
+ iscsi_hashmap_destroy( session->key_value_pairs );
+ iscsi_hashmap_key_destroy( conn_key );
+ iscsi_hashmap_destroy( session->connections );
+ free( session );
+
+ return NULL;
+ }
+
+ return session;
+}
+
+/**
+ * @brief Deallocates all resources acquired by iscsi_session_create.
*
- * Creates a data structure for an incoming iSCSI connection request
- * from iSCSI packet data.
+ * This function also frees the associated key and value pairs,
+ * the attached connections as well as frees the initator port.
*
- * @param[in] login_req_pkt Pointer to iSCSI packet data to construct iSCSI
- * connection request from. May be NULL in which case this function does
- * nothing.
+ * @param[in] session iSCSI session to be freed. May be NULL
+ * in which case this function does nothing at all.
+ */
+void iscsi_session_destroy(iscsi_session *session)
+{
+ if ( session != NULL ) {
+ session->tag = 0L;
+ session->target = NULL;
+ session->type = ISCSI_SESSION_TYPE_INVALID;
+
+ if ( session->key_value_pairs != NULL ) {
+ iscsi_hashmap_iterate( session->key_value_pairs, iscsi_hashmap_key_destroy_value_callback, NULL );
+ iscsi_hashmap_destroy( session->key_value_pairs );
+
+ session->key_value_pairs = NULL;
+ }
+
+ if ( session->connections != NULL ) {
+ iscsi_hashmap_iterate( session->connections, iscsi_connection_destroy_callback, NULL );
+ iscsi_hashmap_destroy( session->connections );
+
+ session->connections = NULL;
+ }
+
+ if ( session->initiator_port != NULL ) {
+ iscsi_port_destroy( session->initiator_port );
+
+ session->initiator_port = NULL;
+ }
+
+ free( session ); // TODO: Check if potential reusage of session makes sense.
+ }
+}
+
+/**
+ * @brief Creates data structure for an iSCSI connection from iSCSI portal and TCP/IP socket.
+ *
+ * Creates a data structure for incoming iSCSI connection
+ * requests from iSCSI packet data.
+ *
+ * @param[in] portal Pointer to iSCSI portal to associate the
+ * connection with.
+ * @param[in] sock TCP/IP socket to associate the connection with.
* @return Pointer to initialized iSCSI connection structure or NULL in
* case of an error (invalid iSCSI packet data or memory exhaustion).
*/
-iscsi_connection *iscsi_connection_create(const iscsi_login_req_packet *login_req_pkt)
+iscsi_connection *iscsi_connection_create(iscsi_portal *portal, const int sock)
{
iscsi_connection *conn = (iscsi_connection *) malloc( sizeof(struct iscsi_connection) );
@@ -1830,6 +2392,7 @@ iscsi_connection *iscsi_connection_create(const iscsi_login_req_packet *login_re
return NULL;
}
+ conn->session = NULL;
conn->key_value_pairs = iscsi_hashmap_create( 32UL );
if ( conn->key_value_pairs == NULL ) {
@@ -1840,21 +2403,54 @@ iscsi_connection *iscsi_connection_create(const iscsi_login_req_packet *login_re
return NULL;
}
- conn->header_digest = 0L;
- conn->data_digest = 0L;
+ conn->partial_pairs = NULL;
+ conn->header_digest = 0L;
+ conn->data_digest = 0L;
+ conn->pdu_processing = NULL;
+ conn->login_response_pdu = NULL;
+ conn->sock = sock;
+ conn->pdu_recv_state = ISCSI_CONNECT_PDU_RECV_STATE_WAIT_PDU_READY;
+ conn->flags = 0L;
+ conn->state = ISCSI_CONNECT_STATE_INVALID;
+ conn->max_recv_ds_len = ISCSI_DEFAULT_RECV_DS_LEN;
+ conn->pg_tag = portal->group->tag;
+ conn->isid.a = 0;
+ conn->isid.b = 0;
+ conn->isid.c = 0;
+ conn->isid.d = 0;
+ conn->tsih = 0U;
+ conn->init_task_tag = 0UL;
+ conn->cid = 0U;
+ conn->stat_sn = 0UL;
+ conn->exp_stat_sn = 0UL;
- conn->isid.a = login_req_pkt->isid.a;
- conn->isid.b = iscsi_get_be16(login_req_pkt->isid.b);
- conn->isid.c = login_req_pkt->isid.c;
- conn->isid.d = iscsi_get_be16(login_req_pkt->isid.d);
+ return conn;
+}
- conn->tsih = login_req_pkt->tsih; // Identifier, no need for endianess conversion
- conn->init_task_tag = login_req_pkt->init_task_tag; // Identifier, no need for endianess conversion
- conn->cid = login_req_pkt->cid; // Identifier, no need for endianess conversion
- conn->cmd_sn = iscsi_get_be32(login_req_pkt->cmd_sn);
- conn->exp_stat_sn = iscsi_get_be32(login_req_pkt->exp_stat_sn);
+/**
+ * @brief iSCSI connection destructor callback for hash map.
+ *
+ * Callback function for deallocation of an iSCSI
+ * connection stored in the hash map managing all
+ * iSCSI connections.
+ *
+ * @param[in] key Pointer to zero padded key. NULL is
+ * an invalid pointer here, so be careful.
+ * @param[in] key_size Number of bytes for the key, MUST
+ * be a multiple of 8 bytes which is NOT checked, so
+ * be careful.
+ * @param[in] value Value of the key, NULL is allowed.
+ * @param[in,out] user_data This argument is not used by
+ * this function and should be always NULL for now, as
+ * there is a possibility for future usage.
+ * @return Always returns 0 as this function cannot fail.
+ */
+int iscsi_connection_destroy_callback(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data)
+{
+ iscsi_connection_destroy( (iscsi_connection *) value );
+ iscsi_hashmap_key_destroy( key );
- return conn;
+ return 0L;
}
/**
@@ -1873,6 +2469,13 @@ iscsi_connection *iscsi_connection_create(const iscsi_login_req_packet *login_re
void iscsi_connection_destroy(iscsi_connection *conn)
{
if ( conn != NULL ) {
+ if ( conn->partial_pairs != NULL ) {
+ free ( conn->partial_pairs );
+
+ conn->partial_pairs = NULL;
+ }
+
+
if ( conn->key_value_pairs != NULL ) {
iscsi_hashmap_iterate( conn->key_value_pairs, iscsi_hashmap_key_destroy_value_callback, NULL );
iscsi_hashmap_destroy( conn->key_value_pairs );
@@ -1885,27 +2488,947 @@ void iscsi_connection_destroy(iscsi_connection *conn)
}
/**
- * @brief iSCSI connection destructor callback for hash map.
+ * @brief Reads data for the specified iSCSI connection from its TCP socket.
*
- * Callback function for deallocation of an iSCSI
- * connection stored in the hash map managing all
- * iSCSI connections.
+ * The TCP socket is marked as non-blocking, so this function may not read
+ * all data requested.
*
- * @param[in] key Pointer to zero padded key. NULL is
- * an invalid pointer here, so be careful.
- * @param[in] key_size Number of bytes for the key, MUST
- * be a multiple of 8 bytes which is NOT checked, so
- * be careful.
- * @param[in] value Value of the key, NULL is allowed.
- * @param[in,out] user_data This argument is not used by
- * this function and should be always NULL for now, as
- * there is a possibility for future usage.
- * @return Always returns 0 as this function cannot fail.
+ * Returns SPDK_ISCSI_CONNECTION_FATAL if the recv() operation indicates a fatal
+ * error with the TCP connection (including if the TCP connection was closed
+ * unexpectedly.
+ *
+ * Otherwise returns the number of bytes successfully read.
*/
-int iscsi_connection_destroy_callback(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data)
+int iscsi_connection_read(const iscsi_connection *conn, uint8_t *buf, const uint len)
{
- iscsi_connection_destroy( (iscsi_connection *) value );
- iscsi_hashmap_key_destroy( key );
+ if ( len == 0 )
+ return 0;
+
+ const int rc = recv( conn->sock, buf, len, MSG_WAITALL );
+
+ return (rc > 0) ? rc : ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+}
+
+/**
+ * @brief Prepares an iSCSI login response PDU and sends it via TCP/IP.
+ *
+ * This function constructs the login response PDU
+ * to be sent via TCP/IP.
+ *
+ * @param[in] conn Pointer to ISCSI connection to send the TCP/IP
+ * packet with.
+ * @param[in] login_response_pdu Pointer to login response PDU to
+ * be sent via TCP/IP.
+ * @param[in] key_value_pairs Pointer to hash map of key and value pairs
+ * to be used for login response storage.
+ * @paran[in] callback Pointer to post processing callback function
+ * after sending the TCP/IP packet.
+ */
+static void iscsi_connection_pdu_login_response(iscsi_connection *conn, iscsi_pdu *login_response_pdu, iscsi_hashmap *key_value_pairs, iscsi_connection_xfer_complete_callback callback)
+{
+ iscsi_login_response_packet *login_response_pkt = (iscsi_login_response_packet *) login_response_pdu->bhs_pkt;
+
+ login_response_pkt->version_max = ISCSI_VERSION_MAX;
+ login_response_pkt->version_active = ISCSI_VERSION_MAX;
+
+ iscsi_put_be24( &login_response_pkt->ds_len, login_response_pdu->ds_len );
+ iscsi_put_be32( &login_response_pkt->stat_sn, conn->stat_sn++ );
+
+ if ( conn->session != NULL ) {
+ iscsi_put_be32( &login_response_pkt->exp_cmd_sn, conn->session->exp_cmd_sn );
+ iscsi_put_be32( &login_response_pkt->max_cmd_sn, conn->session->max_cmd_sn );
+ } else {
+ iscsi_put_be32( &login_response_pkt->exp_cmd_sn, login_response_pdu->cmd_sn );
+ iscsi_put_be32( &login_response_pkt->max_cmd_sn, login_response_pdu->cmd_sn );
+ }
+
+ if ( login_response_pkt->status_class != ISCSI_LOGIN_RESPONSE_STATUS_CLASS_SUCCESS )
+ login_response_pkt->flags &= ~(ISCSI_LOGIN_RESPONSE_FLAGS_TRANSMIT | ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_MASK | ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_MASK );
+
+ iscsi_hashmap_iterate( key_value_pairs, iscsi_hashmap_key_destroy_value_callback, NULL );
+ iscsi_hashmap_destroy( key_value_pairs );
+
+ iscsi_connection_pdu_write( conn, login_response_pdu, callback, (uint8_t *) conn );
+}
+
+/**
+ * @brief Callback function after login response has been sent.
+ *
+ * This function is invoked after the login
+ * response has been sent to the client via
+ * TCP/IP.
+ *
+ * @param[in] user_data Pointer to iSCSI connection which
+ * was used for sending the response.
+ */
+void iscsi_connection_pdu_login_err_complete(uint8_t *user_data)
+{
+ // TODO: Implement login response error or complete.
+}
+
+/**
+ * @brief Creates and initializes an iSCSI login response PDU structure.
+ *
+ * This function creates and initializes the internal
+ * response data structure which is part of the iSCSI
+ * login procedure.
+ *
+ * @param[in] pdu Pointer to PDU, may NOT be NULL, so take
+ * caution.
+ * @param[in] login_req_pkt Pointer to login request from client,
+ * may NOT be NULL, so be careful.
+ * @return Pointer to initialized login response PDU.
+ */
+static iscsi_login_response_packet *iscsi_login_response_create(iscsi_pdu *pdu, const iscsi_login_req_packet *login_req_pkt)
+{
+ iscsi_login_response_packet *bhs_pkt = (iscsi_login_response_packet *) iscsi_create_packet();
+
+ if ( bhs_pkt == NULL )
+ return NULL;
+
+ bhs_pkt->opcode = ISCSI_SERVER_LOGIN_RES;
+
+ iscsi_login_response_packet *login_response_pkt = iscsi_append_ds_packet( bhs_pkt, pdu->header_digest_size, ISCSI_DEFAULT_RECV_DS_LEN, pdu->data_digest_size );
+
+ if ( login_response_pkt == NULL ) {
+ bhs_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_SERVER_ERR;
+ bhs_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SERVER_ERR_OUT_OF_RESOURCES;
+
+ return bhs_pkt;
+ }
+
+ login_response_pkt->flags |= (login_req_pkt->flags & (ISCSI_LOGIN_REQ_FLAGS_TRANSMIT | ISCSI_LOGIN_REQ_FLAGS_CONTINUE | ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_MASK));
+
+ if ( (login_response_pkt->flags & ISCSI_LOGIN_RESPONSE_FLAGS_TRANSMIT) != 0 )
+ login_response_pkt->flags |= (login_req_pkt->flags & ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_MASK);
+
+ login_response_pkt->isid.a = login_req_pkt->isid.a;
+ login_response_pkt->isid.b = login_req_pkt->isid.b; // Copying over doesn't change endianess.
+ login_response_pkt->isid.c = login_req_pkt->isid.c;
+ login_response_pkt->isid.d = login_req_pkt->isid.d; // Copying over doesn't change endianess.
+ login_response_pkt->tsih = login_req_pkt->tsih; // Copying over doesn't change endianess.'
+ login_response_pkt->init_task_tag = login_req_pkt->init_task_tag; // Copying over doesn't change endianess.
+ pdu->cmd_sn = login_req_pkt->cmd_sn;
+
+ if ( login_response_pkt->tsih != 0 )
+ login_response_pkt->stat_sn = iscsi_get_be32(login_req_pkt->exp_stat_sn);
+
+ if ( ((login_response_pkt->flags & ISCSI_LOGIN_RESPONSE_FLAGS_TRANSMIT) != 0) && ((login_response_pkt->flags & ISCSI_LOGIN_RESPONSE_FLAGS_CONTINUE) != 0) ) {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISC;
+ } else if ( (ISCSI_VERSION_MIN < login_req_pkt->version_min) || (ISCSI_VERSION_MAX > login_req_pkt->version_max) ) {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_WRONG_VERSION;
+ } else if ( (ISCSI_LOGIN_RESPONSES_FLAGS_GET_NEXT_STAGE(login_response_pkt->flags) == ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_RESERVED) && ((login_response_pkt->flags & ISCSI_LOGIN_RESPONSE_FLAGS_TRANSMIT) != 0) ) {
+ login_response_pkt->flags &= ~(ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_MASK | ISCSI_LOGIN_RESPONSE_FLAGS_TRANSMIT | ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_MASK);
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISC;
+ } else {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_SUCCESS;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SUCCESS;
+ }
+
+ return login_response_pkt;
+}
+
+/**
+ * @brief Saves incoming key / value pairs from the client of a login request packet.
+ *
+ * The login response structure has status detail
+ * invalid login request type set in case of an error.
+ *
+ * @param[in] conn Pointer to iSCSI connection
+ * used for key and value pair extraction.
+ * @param[out] key_value_pairs Pointer to hash map which
+ * stores all the parsed key and value pairs.
+ * @param[in] login_response_pkt Pointer to iSCSI login response
+ * packet data, may NOT be NULL, so be careful.
+ * @param[in] login_req_pkt Pointer to iSCSI login request packet
+ * data, may NOT be NULL, so be careful.
+ * @retval -1 An error occured during parse of
+ * key and value pairs (memory exhaustion).
+ * @retval 0 All key and value pairs have been parsed successfully.
+ */
+int iscsi_connection_save_incoming_key_value_pairs(iscsi_connection *conn, iscsi_hashmap *key_value_pairs, iscsi_login_response_packet *login_response_pkt, const iscsi_login_req_packet *login_req_pkt)
+{
+ const uint32_t ds_idx = (const uint32_t) sizeof(struct iscsi_bhs_packet) + ((const uint32_t) login_req_pkt->total_ahs_len << 2UL) + conn->header_digest;
+ const uint8_t *login_data = ((uint8_t *) login_req_pkt) + ds_idx;
+ const uint32_t login_len = iscsi_get_be24(login_req_pkt->ds_len);
+
+ if ( iscsi_parse_key_value_pairs( key_value_pairs, login_data, login_len, ((login_req_pkt->flags & ISCSI_LOGIN_REQ_FLAGS_CONTINUE) != 0), &conn->partial_pairs ) < 0L ) {
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISC;
+
+ return -1L;
+ }
+
+ return 0L;
+}
+
+/**
+ * @brief Creates a rejecting login response packet.
+ *
+ * The login response structure has status detail
+ * invalid login request type set.
+ *
+ * @param[in] login_req_pkt Pointer to iSCSI login request packet data, may NOT
+ * be NULL, so be careful.
+ * @return Pointer to initialized iSCSI login response packet data
+ * or NULL in case of memory exhaustion.
+ */
+static iscsi_login_response_packet *iscsi_login_response_reject_create(const iscsi_login_req_packet *login_req_pkt)
+{
+ iscsi_login_response_packet *login_response_pkt = (iscsi_login_response_packet *) iscsi_create_packet();
+
+ if ( login_response_pkt == NULL ) {
+ logadd( LOG_ERROR, "iscsi_login_response_reject_create: Out of memory while allocating iSCSI rejected login response packet data" );
+
+ return NULL;
+ }
+
+ login_response_pkt->opcode = ISCSI_SERVER_LOGIN_RES;
+ login_response_pkt->version_max = ISCSI_VERSION_MAX;
+ login_response_pkt->version_active = ISCSI_VERSION_MAX;
+ login_response_pkt->init_task_tag = login_req_pkt->init_task_tag;
+ login_response_pkt->status_class = ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR;
+ login_response_pkt->status_detail = ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_INVALID_LOGIN_REQ_TYPE;
+
+ return login_response_pkt;
+}
+
+/**
+ * @brief Creates an iSCSI PDU structure used by connections.
+ *
+ * The PDU structure is used for allowing partial
+ * reading from the TCP/IP socket and correctly
+ * filling the data until everything has been read.
+ *
+ * @param[in] conn Pointer to connection to link the PDU with.
+ * If this is NULL the connection has to be linked later.
+ * @return Pointer to allocated and zero filled PDU or NULL
+ * in case of an error (usually memory exhaustion).
+ */
+iscsi_pdu *iscsi_connection_pdu_create(iscsi_connection *conn)
+{
+ iscsi_pdu *pdu = (iscsi_pdu *) malloc( sizeof(struct iscsi_pdu) );
+
+ if ( pdu == NULL ) {
+ logadd( LOG_ERROR, "iscsi_pdu_create: Out of memory while allocating iSCSI PDU" );
+
+ return NULL;
+ }
+
+ pdu->bhs_pkt = iscsi_create_packet();
+
+ if ( pdu->bhs_pkt == NULL ) {
+ free( pdu );
+
+ return NULL;
+ }
+
+ pdu->ahs_pkt = NULL;
+ pdu->header_digest = NULL;
+ pdu->ds_cmd_data = NULL;
+ pdu->data_digest = NULL;
+ pdu->flags = 0L;
+ pdu->header_digest_size = 0L;
+ pdu->header_digest_read_len = 0UL;
+ pdu->data_digest_size = 0L;
+ pdu->data_digest_read_len = 0UL;
+ pdu->bhs_read_len = 0UL;
+ pdu->ahs_len = 0UL;
+ pdu->ahs_read_len = 0UL;
+ pdu->ds_len = 0UL;
+ pdu->buf_len = 0UL;
+ pdu->conn = conn;
+ pdu->cmd_sn = 0UL;
+
+ return pdu;
+}
+
+/**
+ * @brief Destroys an iSCSI PDU structure used by connections.
+ *
+ * All associated data which has been read so
+ * far will be freed as well.
+ *
+ * @param[in] pdu PDU structure to be deallocated, may be NULL
+ * in which case this function does nothing.
+ */
+void iscsi_connection_pdu_destroy(iscsi_pdu *pdu)
+{
+ if ( pdu != NULL ) {
+ if ( pdu->bhs_pkt != NULL ) {
+ free( pdu->bhs_pkt );
+
+ pdu->bhs_pkt = NULL;
+ }
+
+ free( pdu );
+ }
+}
+
+/**
+ * @brief Writes and sends a response PDU to the client.
+ *
+ * This function sends a response PDU to the
+ * client after being processed by the server.\n
+ * If a header or data digest (CRC32C) needs to
+ * be calculated, this is done as well.
+ *
+ * @param[in] conn iSCSI connection to handle. May
+ * NOT be NULL, so take caution.
+ * @param[in] pdu iSCSI server response PDU to send.
+ * May NOT be NULL, so be careful.
+ * @param[in] callback Callback function to be invoked
+ * after TCP/IP packet has been sent successfully.
+ * May be NULL in case no further action is required.
+ * @param[in,out] user_data Data for the callback
+ * function. May be NULL if the callback function
+ * doesn't require additional data.
+ */
+void iscsi_connection_pdu_write(iscsi_connection *conn, iscsi_pdu *pdu, iscsi_connection_xfer_complete_callback callback, uint8_t *user_data)
+{
+ // TODO: Implement PDU write.
+
+ if ( callback != NULL )
+ callback( user_data );
+}
+
+/**
+ * @brief Constructs and sends an iSCSI reject response to the client.
+ *
+ * This function constructs an reject response PDU with its
+ * packet data.\n
+ * The original rejected packet data is appended as DataSegment
+ * according by iSCSI standard specification.
+ *
+ * @param[in] conn Pointer to iSCSI connection for reject packet construction.
+ * @param[in] pdu Pointer to iSCSI source PDU which contains the rejected packet data.
+ * @param[in] reason_code Reason code for rejected packet data.
+ * @retval -1 An error occured during reject packet generation,
+ * currently only happens on memory exhaustion.
+ * @retval 0 Reject packet and PDU constructed and sent successfully to the client.
+ */
+static int iscsi_connection_handle_reject(iscsi_connection *conn, iscsi_pdu *pdu, const int reason_code)
+{
+ pdu->flags |= ISCSI_PDU_FLAGS_REJECTED;
+
+ iscsi_pdu *response_pdu = iscsi_connection_pdu_create( conn );
+
+ if ( response_pdu == NULL ) {
+ logadd( LOG_ERROR, "iscsi_pdu_create: Out of memory while allocating iSCSI reject response PDU" );
+
+ return -1L;
+ }
+
+ const uint32_t ds_len = sizeof(struct iscsi_bhs_packet) + ((uint32_t) pdu->bhs_pkt->total_ahs_len << 2UL) + conn->header_digest;
+ iscsi_reject_packet *reject_pkt = (iscsi_reject_packet *) iscsi_append_ds_packet( pdu->bhs_pkt, conn->header_digest, ds_len, conn->data_digest );
+
+ if ( reject_pkt == NULL ) {
+ logadd( LOG_ERROR, "iscsi_pdu_create: Out of memory while allocating iSCSI reject packet data" );
+
+ iscsi_connection_pdu_destroy( response_pdu );
+
+ return -1L;
+ }
+
+ response_pdu->bhs_pkt = (iscsi_bhs_packet *) reject_pkt;
+
+ if ( conn->header_digest != 0 ) {
+ response_pdu->header_digest = ((iscsi_bhs_packet *) reject_pkt) + 1;
+ response_pdu->header_digest_size = conn->header_digest;
+ }
+
+ response_pdu->ds_cmd_data = ((uint8_t *) reject_pkt) + sizeof(struct iscsi_bhs_packet) + conn->header_digest;
+ response_pdu->ds_len = ds_len;
+
+ if ( conn->data_digest != 0 ) {
+ response_pdu->data_digest = ((uint8_t *) response_pdu->ds_cmd_data) + iscsi_align(ds_len, ISCSI_ALIGN_SIZE);
+ response_pdu->data_digest_size = conn->data_digest;
+ }
+
+ reject_pkt->opcode = ISCSI_SERVER_REJECT;
+ reject_pkt->flags |= -0x80;
+ reject_pkt->reason = reason_code;
+ iscsi_put_be24( &reject_pkt->ds_len, ds_len );
+ reject_pkt->tag = 0xFFFFFFFFUL;
+ iscsi_put_be32( &reject_pkt->stat_sn, conn->stat_sn++ );
+
+ if ( conn->session != NULL ) {
+ iscsi_put_be32( &reject_pkt->exp_cmd_sn, conn->session->exp_cmd_sn );
+ iscsi_put_be32( &reject_pkt->max_cmd_sn, conn->session->max_cmd_sn );
+ } else {
+ iscsi_put_be32( &reject_pkt->exp_cmd_sn, 1 );
+ iscsi_put_be32( &reject_pkt->max_cmd_sn, 1 );
+ }
+
+ memcpy( ((uint8_t *) reject_pkt) + sizeof(struct iscsi_bhs_packet), pdu->bhs_pkt, ds_len );
+
+ iscsi_connection_pdu_write( conn, response_pdu, NULL, NULL );
+
+ return 0L;
+}
+
+/**
+ * @brief Updates Command Sequence Number (CmdSN) of an incoming iSCSI PDU request.
+ *
+ * This function updates the Command Sequence
+ * Number (CmdSN) for incoming data sent by
+ * the client.
+ *
+ * @param[in] conn iSCSI connection to handle. May
+ * NOT be NULL, so take caution.
+ * @param[in] pdu iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_update_cmd_sn(iscsi_connection *conn, iscsi_pdu *pdu)
+{
+ // TODO: Implement update CmdSN.
+
+ return 0L;
+}
+
+/**
+ * @brief Handles an incoming iSCSI header login request PDU.
+ *
+ * This function handles login request header
+ * data sent by the client.\n
+ * If a response needs to be sent, this will
+ * be done as well.
+ *
+ * @param[in] conn iSCSI connection to handle. May
+ * NOT be NULL, so take caution.
+ * @param[in] pdu iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_pdu_header_handle_login_req(iscsi_connection *conn, iscsi_pdu *pdu)
+{
+ if ( ((conn->flags & ISCSI_CONNECT_FLAGS_FULL_FEATURE) != 0) && (conn->session != NULL) && (conn->session->type == ISCSI_SESSION_TYPE_DISCOVERY) )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ const iscsi_login_req_packet *login_req_pkt = (iscsi_login_req_packet *) pdu->bhs_pkt;
+
+ pdu->cmd_sn = iscsi_get_be32(login_req_pkt->cmd_sn);
+
+ if ( pdu->ds_len > ISCSI_DEFAULT_RECV_DS_LEN )
+ return iscsi_connection_handle_reject( conn, pdu, ISCSI_REJECT_REASON_PROTOCOL_ERR );
+
+ iscsi_pdu *login_response_pdu = iscsi_connection_pdu_create( conn );
+
+ if ( login_response_pdu == NULL )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ iscsi_login_response_packet *login_response_pkt = iscsi_login_response_create( conn, pdu );
+
+ if ( login_response_pkt == NULL ) {
+ iscsi_connection_pdu_login_response( conn, login_response_pdu, NULL, iscsi_connection_pdu_login_err_complete );
+
+ return ISCSI_CONNECT_PDU_READ_OK;
+ }
+
+ conn->login_response_pdu = login_response_pdu;
+
+ return ISCSI_CONNECT_PDU_READ_OK;
+}
+
+/**
+ * @brief Handles an incoming iSCSI header NOP-Out request PDU.
+ *
+ * This function handles NOP-Out request header
+ * data sent by the client.\n
+ * If a response needs to be sent, this will
+ * be done as well.
+ *
+ * @param[in] conn iSCSI connection to handle. May
+ * NOT be NULL, so take caution.
+ * @param[in] pdu iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_pdu_header_handle_nop_out(iscsi_connection *conn, iscsi_pdu *pdu)
+{
+ // TODO: Implement opcode.
+
+ return 0;
+}
+
+/**
+ * @brief Handles an incoming iSCSI header SCSI command request PDU.
+ *
+ * This function handles SCSI command request
+ * header data sent by the client.\n
+ * If a response needs to be sent, this will
+ * be done as well.
+ *
+ * @param[in] conn iSCSI connection to handle. May
+ * NOT be NULL, so take caution.
+ * @param[in] pdu iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_pdu_header_handle_scsi_cmd(iscsi_connection *conn, iscsi_pdu *pdu)
+{
+ // TODO: Implement opcode.
+
+ return 0;
+}
+
+/**
+ * @brief Handles an incoming iSCSI header task management function request PDU.
+ *
+ * This function handles task management function
+ * request header data sent by the client.\n
+ * If a response needs to be sent, this will
+ * be done as well.
+ *
+ * @param[in] conn iSCSI connection to handle. May
+ * NOT be NULL, so take caution.
+ * @param[in] pdu iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_pdu_header_handle_task_func_req(iscsi_connection *conn, iscsi_pdu *pdu)
+{
+ // TODO: Implement opcode.
+
+ return 0;
+}
+
+/**
+ * @brief Handles an incoming iSCSI header text request PDU.
+ *
+ * This function handles text request header
+ * data sent by the client.\n
+ * If a response needs to be sent, this will
+ * be done as well.
+ *
+ * @param[in] conn iSCSI connection to handle. May
+ * NOT be NULL, so take caution.
+ * @param[in] pdu iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_pdu_header_handle_text_req(iscsi_connection *conn, iscsi_pdu *pdu)
+{
+ // TODO: Implement opcode.
+
+ return 0;
+}
+
+/**
+ * @brief Handles an incoming iSCSI header logout request PDU.
+ *
+ * This function handles logout request header
+ * data sent by the client.\n
+ * If a response needs to be sent, this will
+ * be done as well.
+ *
+ * @param[in] conn iSCSI connection to handle. May
+ * NOT be NULL, so take caution.
+ * @param[in] pdu iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_pdu_header_handle_logout_req(iscsi_connection *conn, iscsi_pdu *pdu)
+{
+ // TODO: Implement opcode.
+
+ return 0;
+}
+
+/**
+ * @brief Handles an incoming iSCSI header SCSI data out PDU.
+ *
+ * This function handles header SCSI data out
+ * sent by the client.\n
+ * If a response needs to be sent, this will
+ * be done as well.
+ *
+ * @param[in] conn iSCSI connection to handle. May
+ * NOT be NULL, so take caution.
+ * @param[in] pdu iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_pdu_header_handle_scsi_data_out(iscsi_connection *conn, iscsi_pdu *pdu)
+{
+ // TODO: Implement opcode.
+
+ return 0;
+}
+
+/**
+ * @brief Handles an incoming iSCSI header SNACK request PDU.
+ *
+ * This function handles SNACK request header
+ * data sent by the client.\n
+ * If a response needs to be sent, this will
+ * be done as well.
+ *
+ * @param[in] conn iSCSI connection to handle. May
+ * NOT be NULL, so take caution.
+ * @param[in] pdu iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_pdu_header_handle_snack_req(iscsi_connection *conn, iscsi_pdu *pdu)
+{
+ // TODO: Implement opcode.
+
+ return 0;
+}
+
+/**
+ * @brief Handles an incoming iSCSI header PDU.
+ *
+ * This function handles all header data sent
+ * by the client, including authentication.\n
+ * If a response needs to be sent, this will
+ * be done as well.
+ *
+ * @param[in] conn iSCSI connection to handle. May
+ * NOT be NULL, so take caution.
+ * @param[in] pdu iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_pdu_header_handle(iscsi_connection *conn, iscsi_pdu *pdu)
+{
+ if ( pdu == NULL )
+ return -1L;
+
+ const int opcode = ISCSI_GET_OPCODE(pdu->bhs_pkt->opcode);
+
+ if ( opcode == ISCSI_CLIENT_LOGIN_REQ )
+ return iscsi_connection_pdu_header_handle_login_req( conn, pdu );
+
+ if ( ((conn->flags & ISCSI_CONNECT_FLAGS_FULL_FEATURE) == 0) && (conn->state == ISCSI_CONNECT_STATE_RUNNING) ) {
+ iscsi_pdu *login_response_pdu = iscsi_connection_pdu_create( conn );
+
+ if ( login_response_pdu == NULL )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ iscsi_login_response_packet *login_response_pkt = iscsi_login_response_reject_create( (iscsi_login_req_packet *) pdu->bhs_pkt );
+
+ iscsi_connection_pdu_write( conn, login_response_pdu, NULL, NULL );
+
+ return ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE;
+ } else if ( conn->state == ISCSI_CONNECT_STATE_INVALID ) {
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+ }
+
+ int rc = iscsi_connection_update_cmd_sn( conn, pdu );
+
+ if ( rc != 0 )
+ return rc;
+
+ switch ( opcode ) {
+ case ISCSI_CLIENT_NOP_OUT : {
+ rc = iscsi_connection_pdu_header_handle_nop_out( conn, pdu );
+
+ break;
+ }
+ case ISCSI_CLIENT_SCSI_CMD : {
+ rc = iscsi_connection_pdu_header_handle_scsi_cmd( conn, pdu );
+
+ break;
+ }
+ case ISCSI_CLIENT_TASK_FUNC_REQ : {
+ rc = iscsi_connection_pdu_header_handle_task_func_req( conn, pdu );
+
+ break;
+ }
+ case ISCSI_CLIENT_TEXT_REQ : {
+ rc = iscsi_connection_pdu_header_handle_text_req( conn, pdu );
+
+ break;
+ }
+ case ISCSI_CLIENT_LOGOUT_REQ : {
+ rc = iscsi_connection_pdu_header_handle_logout_req( conn, pdu );
+
+ break;
+ }
+ case ISCSI_CLIENT_SCSI_DATA_OUT : {
+ rc = iscsi_connection_pdu_header_handle_scsi_data_out( conn, pdu );
+
+ break;
+ }
+ case ISCSI_CLIENT_SNACK_REQ : {
+ rc = iscsi_connection_pdu_header_handle_snack_req( conn, pdu );
+
+ break;
+ }
+ default : {
+ return iscsi_connection_handle_reject( conn, pdu, ISCSI_REJECT_REASON_PROTOCOL_ERR );
+
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * @brief Handles an incoming iSCSI payload data PDU.
+ *
+ * This function handles all payload data sent
+ * by the client.\n
+ * If a response needs to be sent, this will
+ * be done as well.
+ *
+ * @param[in] conn iSCSI connection to handle. May
+ * NOT be NULL, so take caution.
+ * @param[in] pdu iSCSI client request PDU to handle.
+ * May be NULL in which case an error is returned.
+ * @return 0 on success. A negative value indicates
+ * an error. A positive value a warning.
+ */
+static int iscsi_connection_pdu_data_handle(iscsi_connection *conn, iscsi_pdu *pdu)
+{
+ // TODO: Implement PDU payload data handler.
return 0L;
}
+
+/**
+ * @brief Retrieves and merges splitted iSCSI PDU data read from TCP/IP socket.
+ *
+ * This function handles partial reads of data
+ * packet.\n
+ * Since iSCSI data can span multiple packets, not
+ * only by TCP/IP itself, but also by iSCSI protocol
+ * specifications, multiple calls are needed in order
+ * to be sure that all data packets have been
+ * received.
+ *
+ * @param[in] conn iSCSI connection to read TCP/IP data from.
+ * @param[in] pdu iSCSI PDU to read TCP/IP data into.
+ * @retval -1 Fatal error occured during processing the PDU.
+ * @retval 0 Read operation was successful and next read is ready.
+ * @retval 1 Read operation was successful and PDU was fully processed.
+ */
+int iscsi_connection_pdu_data_read(iscsi_connection *conn, iscsi_pdu *pdu)
+{
+ // TODO: Implement DS read.
+
+ return 0;
+}
+
+/**
+ * @brief Retrieves and merges splitted iSCSI PDU data read from TCP/IP socket.
+ *
+ * This function handles partial reads of BHS, AHS
+ * and DS packet data.\n
+ * Since iSCSI data can span multiple packets, not
+ * only by TCP/IP itself, but also by iSCSI protocol
+ * specifications, multiple calls are needed in order
+ * to be sure that all data packets have been
+ * received.
+ *
+ * @param[in] conn iSCSI connection to read TCP/IP data from.
+ * @retval -1 Fatal error occured during processing the PDU.
+ * @retval 0 Read operation was successful and next read is ready.
+ * @retval 1 Read operation was successful and PDU was fully processed.
+ */
+static int iscsi_connection_pdu_read(iscsi_connection *conn)
+{
+ int prev_recv_state;
+
+ do {
+ iscsi_pdu *pdu = conn->pdu_processing;
+
+ prev_recv_state = conn->pdu_recv_state;
+
+ switch ( conn->pdu_recv_state ) {
+ case ISCSI_CONNECT_PDU_RECV_STATE_WAIT_PDU_READY : {
+ conn->pdu_processing = iscsi_connection_pdu_create( conn );
+
+ if ( conn->pdu_processing == NULL )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ conn->pdu_recv_state = ISCSI_CONNECT_PDU_RECV_STATE_WAIT_PDU_HDR;
+
+ break;
+ }
+ case ISCSI_CONNECT_PDU_RECV_STATE_WAIT_PDU_HDR : {
+ if ( pdu->bhs_read_len < sizeof(struct iscsi_bhs_packet) ) {
+ const int len = iscsi_connection_read( conn, (((uint8_t *) pdu->bhs_pkt) + pdu->bhs_read_len), (sizeof(struct iscsi_bhs_packet) - pdu->bhs_read_len) );
+
+ if ( len < 0 ) {
+ conn->pdu_recv_state = ISCSI_CONNECT_PDU_RECV_STATE_ERR;
+
+ break;
+ }
+
+ pdu->bhs_read_len += len;
+
+ if ( pdu->bhs_read_len < sizeof(struct iscsi_bhs_packet) )
+ return ISCSI_CONNECT_PDU_READ_OK;
+ }
+
+ if ( (conn->flags & ISCSI_CONNECT_FLAGS_LOGGED_OUT) != 0 ) {
+ conn->pdu_recv_state = ISCSI_CONNECT_PDU_RECV_STATE_ERR;
+
+ break;
+ }
+
+ pdu->ds_len = iscsi_align(pdu->ds_len, ISCSI_ALIGN_SIZE);
+ pdu->buf_len = pdu->ds_len;
+
+ const uint ahs_len = (uint) pdu->bhs_pkt->total_ahs_len << 2UL;
+
+ if ( pdu->ahs_read_len < ahs_len ) {
+ if ( pdu->ahs_pkt == NULL ) {
+ pdu->ahs_pkt = iscsi_append_ahs_packet( pdu->bhs_pkt, (uint32_t) ahs_len );
+
+ if ( pdu->ahs_pkt == NULL )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ pdu->ahs_pkt = ((iscsi_bhs_packet *) pdu->bhs_pkt) + 1;
+ }
+
+ const int len = iscsi_connection_read( conn, (((uint8_t *) pdu->ahs_pkt) + pdu->ahs_read_len), (ahs_len - pdu->ahs_read_len) );
+
+ if ( len < 0 ) {
+ conn->pdu_recv_state = ISCSI_CONNECT_PDU_RECV_STATE_ERR;
+
+ break;
+ }
+
+ pdu->ahs_read_len += len;
+
+ if ( pdu->ahs_read_len < ahs_len )
+ return ISCSI_CONNECT_PDU_READ_OK;
+ }
+
+ if ( conn->header_digest != 0 ) {
+ if ( pdu->header_digest == NULL ) {
+ pdu->header_digest = iscsi_append_header_digest_packet( pdu->bhs_pkt, ISCSI_DIGEST_SIZE );
+
+ if ( pdu->header_digest == NULL )
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ pdu->header_digest = ((uint8_t *) pdu->bhs_pkt) + sizeof(struct iscsi_bhs_packet) + ahs_len;
+ }
+
+ if ( pdu->header_digest_read_len < (uint) conn->header_digest ) {
+ const int len = iscsi_connection_read( conn, (((uint8_t *) pdu->header_digest) + pdu->header_digest_read_len), (conn->header_digest - pdu->header_digest_read_len) );
+
+ if ( len < 0 ) {
+ conn->pdu_recv_state = ISCSI_CONNECT_PDU_RECV_STATE_ERR;
+
+ break;
+ }
+
+ pdu->header_digest_read_len += len;
+
+ if ( pdu->header_digest_read_len < (uint) conn->header_digest )
+ return ISCSI_CONNECT_PDU_READ_OK;
+ }
+
+ if ( iscsi_validate_header_digest( pdu->bhs_pkt ) == 0 ) {
+ conn->pdu_recv_state = ISCSI_CONNECT_PDU_RECV_STATE_ERR;
+
+ break;
+ }
+ }
+
+ conn->pdu_recv_state = (iscsi_connection_pdu_header_handle( conn, pdu ) < 0L) ? ISCSI_CONNECT_PDU_RECV_STATE_ERR : ISCSI_CONNECT_PDU_RECV_STATE_WAIT_PDU_DATA;
+
+ break;
+ }
+ case ISCSI_CONNECT_PDU_RECV_STATE_WAIT_PDU_DATA : {
+ if ( pdu->ds_len != 0 ) {
+ const int len = iscsi_connection_pdu_data_read( conn, pdu );
+
+ if ( len < 0 ) {
+ conn->pdu_recv_state = ISCSI_CONNECT_PDU_RECV_STATE_ERR;
+
+ break;
+ } else if ( len > 0 ) {
+ return ISCSI_CONNECT_PDU_READ_OK;
+ }
+ }
+
+ int rc;
+
+ if ( (conn->flags & ISCSI_CONNECT_FLAGS_REJECTED) != 0 )
+ rc = 0;
+ else
+ rc = iscsi_connection_pdu_data_handle( conn, pdu );
+
+ if ( rc == 0 ) {
+ iscsi_connection_pdu_destroy( pdu );
+
+ conn->pdu_processing = NULL;
+ conn->pdu_recv_state = ISCSI_CONNECT_PDU_RECV_STATE_WAIT_PDU_READY;
+
+ return ISCSI_CONNECT_PDU_READ_PROCESSED;
+ } else {
+ conn->pdu_recv_state = ISCSI_CONNECT_PDU_RECV_STATE_ERR;
+ }
+
+ break;
+ }
+ case ISCSI_CONNECT_PDU_RECV_STATE_ERR : {
+ return ISCSI_CONNECT_PDU_READ_ERR_FATAL;
+
+ break;
+ }
+ default : {
+ logadd( LOG_ERROR, "iscsi_connection_pdu_read: Fatal error reading, unknown packet status. Should NEVER happen! Please report this bug to the developer" );
+
+ break;
+ }
+ }
+ } while ( prev_recv_state != conn->pdu_recv_state );
+
+ return 0;
+}
+
+#define ISCSI_PDU_HANDLE_COUNT 16
+
+/**
+ * @brief Handle incoming PDU data, read up to 16 fragments at once.
+ *
+ * Until iSCSI processing has been stopped or a
+ * complete iSCSI packet has been read, this
+ * function will read, parse and process
+ * incoming iSCSI protocol data.
+ *
+ * @param[in] iSCSI connection to handle.
+ * @return Number of proccessed fragments or return
+ * code of iscsi_connection_pdu_read in case of a
+ * fatal error.
+ */
+int iscsi_connection_pdu_handle(iscsi_connection *conn)
+{
+ uint i;
+
+ for ( i = 0; i < ISCSI_PDU_HANDLE_COUNT; i++ ) {
+ int rc = iscsi_connection_pdu_read(conn);
+
+ if ( rc == 0 )
+ break;
+ else if ( rc < 0 )
+ return rc;
+
+ if ( (conn->flags & ISCSI_CONNECT_FLAGS_STOPPED) != 0 )
+ break;
+ }
+
+ return i;
+}
diff --git a/src/server/iscsi.h b/src/server/iscsi.h
index 690166b..fddf04f 100644
--- a/src/server/iscsi.h
+++ b/src/server/iscsi.h
@@ -123,6 +123,7 @@ uint8_t *iscsi_sprintf_append_realloc(char *buf, const char *format, ...); // Al
uint8_t *iscsi_vsprintf_alloc(const char *format, va_list args); // Allocates a buffer and sprintf's it
uint8_t *iscsi_sprintf_alloc(const char *format, ... ); // Allocates a buffer and sprintf's it
+
/// Shift factor for default capacity.
#define ISCSI_HASHMAP_DEFAULT_CAPACITY_SHIFT 5UL
@@ -138,11 +139,13 @@ uint8_t *iscsi_sprintf_alloc(const char *format, ... ); // Allocates a buffer an
/// Key data size must be multiple of 8 bytes by now.
#define ISCSI_HASHMAP_KEY_ALIGN (1UL << (ISCSI_HASHMAP_KEY_ALIGN_SHIFT))
+
/// Initial hash code.
#define ISCSI_HASHMAP_HASH_INITIAL 0x811C9DC5UL
/// Value to multiply hash code with.
-#define ISCSI_HASHMAP_HASH_MUL 0xBF58476D1CE4E5B9ULL
+#define ISCSI_HASHMAP_HASH_MUL 0xBF58476D1CE4E5B9ULL
+
/**
* @brief Hash map bucket containing key, value and hash code.
@@ -221,25 +224,28 @@ typedef struct iscsi_hashmap {
* 0 means successful operation and a positive value
* indicates a non-fatal error or a warning.
*/
-typedef int (*iscsi_hashmap_callback)(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data); // A Callback for iterating over map, freeing and removing entries.
- // user_data is free for personal use
+typedef int (*iscsi_hashmap_callback)(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data);
iscsi_hashmap *iscsi_hashmap_create(const uint capacity); // Creates an empty hash map with either specified or default capacity
void iscsi_hashmap_destroy(iscsi_hashmap *map); // Deallocates the hash map objects and buckets, not elements
// Use iscsi_hashmap_iterate to deallocate the elements themselves
+
uint8_t *iscsi_hashmap_key_create(const uint8_t *data, const size_t len); // Creates a key suitable for hashmap usage (ensures 8-byte boundary and zero padding)
void iscsi_hashmap_key_destroy(uint8_t *key); // Deallocates all resources acquired by iscsi_hashmap_create_key
int iscsi_hashmap_key_destroy_value_callback(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data); // Deallocates all key / value pairs in a hash map by calling free (default destructor)
+
int iscsi_hashmap_put(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t *value); // Assigns key / value pair to hash map without making copies
int iscsi_hashmap_get_put(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t **out_in_value); // Assigns key / value pair to hash map without making copies
int iscsi_hashmap_put_free(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t *value, iscsi_hashmap_callback callback, uint8_t *user_data); // Assigns key / value pair to hash map without making copies
// with callback function in case the key already exists
int iscsi_hashmap_contains(iscsi_hashmap *map, const uint8_t *key, const size_t key_size); // Checks whether a specified key exists
int iscsi_hashmap_get(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t **out_value); // Retrieves the value of a specified key
+
void iscsi_hashmap_remove(iscsi_hashmap *map, const uint8_t *key, const size_t key_size); // Marks an element for removal by setting key and value both to NULL
void iscsi_hashmap_remove_free(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, iscsi_hashmap_callback callback, uint8_t *user_data); // Marks an element for removal by setting key and value both to NULL,
// but invokes a callback function before actual marking for removal.
int iscsi_hashmap_size(iscsi_hashmap *map); // Retrieves the number of elements of the hash map, ignoring elements marked for removal
+
int iscsi_hashmap_iterate(iscsi_hashmap *map, iscsi_hashmap_callback callback, uint8_t *user_data); // Iterator with callback function invoked on each element which has not been removed
/* iSCSI protocol stuff (all WORD/DWORD/QWORD values are big endian by default
@@ -248,12 +254,23 @@ int iscsi_hashmap_iterate(iscsi_hashmap *map, iscsi_hashmap_callback callback, u
/// iSCSI Basic Header Segment size.
#define ISCSI_BHS_SIZE 48UL
+/// Default receive DataSegment (DS) size in bytes.
+#define ISCSI_DEFAULT_RECV_DS_LEN 8192UL
+
+/// iSCSI default maximum DataSegment receive length in bytes
+#define ISCSI_DEFAULT_MAX_RECV_DS_LEN 65536UL
+
+/// iSCSI default maximum DataSegment receive length in bytes
+#define ISCSI_DEFAULT_MAX_DATA_OUT_PER_CONNECTION 16UL
+
+
/// iSCSI header and data digest size (CRC32C).
#define ISCSI_DIGEST_SIZE 4UL
/// iSCSI packet data alignment (BHS, AHS and DataSegment).
#define ISCSI_ALIGN_SIZE 4UL
+
/// Current minimum iSCSI protocol version supported by this implementation.
#define ISCSI_VERSION_MIN 0
@@ -4197,13 +4214,16 @@ typedef struct __attribute__((packed)) iscsi_isid {
#define ISCSI_LOGIN_AUTH_SESSION_TEXT_KEY_IF_MARK_INT "IFMarkInt"
-/// Login request flags: SecurityNegotiation.
+/// Login request Next Stage (NSG) flags: SecurityNegotiation.
#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_SECURITY_NEGOTIATION 0x0
-/// Login request flags: LoginOperationalNegotiation.
+/// Login request Next Stage (NSG) flags: LoginOperationalNegotiation.
#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_LOGIN_OPERATIONAL_NEGOTIATION 0x1
-/// Login request flags: FullFeaturePhase.
+/// Login request Next Stage (NSG) flags: Reserved for future usage, may NOT be used.
+#define ISCSI_LOGIN_REQ_FLAGS_BEXT_STAGE_RESERVED 0x2
+
+/// Login request Next Stage (NSG) flags: FullFeaturePhase.
#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_FULL_FEATURE_PHASE 0x3
/**
@@ -4215,19 +4235,36 @@ typedef struct __attribute__((packed)) iscsi_isid {
* next stage to which they want to move. The Next Stage value is only
* valid when the T bit is 1; otherwise, it is reserved.
*/
-#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE (1 << 0)
+#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_FIRST_BIT 0
+
+/**
+ * @brief Login request flags: Next Stage (NSG): Second bit of the two bits.
+ *
+ * The Login negotiation requests and responses are associated
+ * with a specific stage in the session (SecurityNegotiation,\n
+ * LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
+ * next stage to which they want to move. The Next Stage value is only
+ * valid when the T bit is 1; otherwise, it is reserved.
+ */
+#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_SECOND_BIT ((ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_FIRST_BIT) + 1)
+
+/// Login request flags: Next Stage (NSG): Bit mask.
+#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_MASK ((1 << (ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_FIRST_BIT)) | (1 << (ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_SECOND_BIT)))
/// Login request flags: Extracts the Next Stage (NSG) bits.
-#define ISCSI_LOGIN_REQS_FLAGS_GET_NEXT_STAGE(x) ((x) & 3)
+#define ISCSI_LOGIN_REQ_FLAGS_GET_NEXT_STAGE(x) (((x) & ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_MASK) >> ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_FIRST_BIT)
-/// Login request flags: SecurityNegotiation.
+/// Login request Current Stage (CSG) flags: SecurityNegotiation.
#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_SECURITY_NEGOTIATION 0x0
-/// Login request flags: LoginOperationalNegotiation.
+/// Login request Current Stage (CSG) flags: LoginOperationalNegotiation.
#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_LOGIN_OPERATIONAL_NEGOTIATION 0x1
-/// Login request flags: FullFeaturePhase.
+/// Login request Current Stage (CSG) flags: Reserved for future usage, may NOT be used.
+#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_RESERVED 0x2
+
+/// Login request Current Stage (CSG) flags: FullFeaturePhase.
#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_FULL_FEATURE_PHASE 0x3
/**
@@ -4238,10 +4275,23 @@ typedef struct __attribute__((packed)) iscsi_isid {
* LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
* next stage to which they want to move.
*/
-#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE (1 << 2)
+#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_FIRST_BIT 2
+
+/**
+ * @brief Login request flags: Current Stage (CSG): Second bit of the two bits.
+ *
+ * The Login negotiation requests and responses are associated
+ * with aspecific stage in the session (SecurityNegotiation,
+ * LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
+ * next stage to which they want to move.
+ */
+#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_SECOND_BIT ((ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_FIRST_BIT) + 1)
+
+/// Login request flags: Current Stage (CSG): Bit mask.
+#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_MASK ((1 << (ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_FIRST_BIT)) | (1 << (ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_SECOND_BIT)))
/// Login request flags: Extracts the Current Stage (CSG) bits.
-#define ISCSI_LOGIN_REQS_FLAGS_GET_CURRENT_STAGE(x) (((x) >> 2) & 3)
+#define ISCSI_LOGIN_REQ_FLAGS_GET_CURRENT_STAGE(x) (((x) & ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_MASK) >> ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_FIRST_BIT)
/**
@@ -4396,13 +4446,16 @@ typedef struct __attribute__((packed)) iscsi_login_req_packet {
} iscsi_login_req_packet;
-/// Login response flags: SecurityNegotiation.
+/// Login response Next Stage (NSG) flags: SecurityNegotiation.
#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_SECURITY_NEGOTIATION 0x0
-/// Login response flags: LoginOperationalNegotiation.
+/// Login response Next Stage (NSG) flags: LoginOperationalNegotiation.
#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_LOGIN_OPERATIONAL_NEGOTIATION 0x1
-/// Login response flags: FullFeaturePhase.
+/// Login response Next Stage (NSG) flags: Reserved for future usage, may NOT be used.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_RESERVED 0x2
+
+/// Login response Next Stage (NSG) flags: FullFeaturePhase.
#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FULL_FEATURE_PHASE 0x3
/**
@@ -4414,18 +4467,37 @@ typedef struct __attribute__((packed)) iscsi_login_req_packet {
* next stage to which they want to move The Next Stage value is only
* valid when the T bit is 1; otherwise, it is reserved.
*/
-#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE (1 << 0)
+#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FIRST_BIT 0
+
+/**
+ * @brief Login response flags: Next Stage (NSG): First bit of the two bits.
+ *
+ * The Login negotiation requests and responses are associated
+ * with a specific stage in the session (SecurityNegotiation,
+ * LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
+ * next stage to which they want to move The Next Stage value is only
+ * valid when the T bit is 1; otherwise, it is reserved.
+ */
+#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_SECOND_BIT ((ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FIRST_BIT) + 1)
+
+/// Login response flags: Next Stage (NSG): Bit mask.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_MASK ((1 << (ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FIRST_BIT)) | (1 << (ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_SECOND_BIT)))
/// Login response flags: Extracts the Next Stage (NSG) bits.
-#define ISCSI_LOGIN_RESPONSE_FLAGS_GET_NEXT_STAGE(x) ((x) & 3)
+#define ISCSI_LOGIN_RESPONSES_FLAGS_GET_NEXT_STAGE(x) (((x) & ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_MASK) >> ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FIRST_BIT)
+
+
-/// Login response flags: SecurityNegotiation.
+/// Login response Current Stage (CSG) flags: SecurityNegotiation.
#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_SECURITY_NEGOTIATION 0x0
-/// Login response flags: LoginOperationalNegotiation.
+/// Login response Current Stage (CSG) flags: LoginOperationalNegotiation.
#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_LOGIN_OPERATIONAL_NEGOTIATION 0x1
-/// Login response flags: FullFeaturePhase.
+/// Login response Current Stage (CSG) flags: Reserved for future usage, may NOT be used.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_RESERVED 0x2
+
+/// Login response Current Stage (CSG) flags: FullFeaturePhase.
#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FULL_FEATURE_PHASE 0x3
/**
@@ -4436,10 +4508,24 @@ typedef struct __attribute__((packed)) iscsi_login_req_packet {
* LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
* next stage to which they want to move.
*/
-#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE (1 << 2)
+#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FIRST_BIT 2
+
+/**
+ * @brief Login response flags: Current Stage (CSG): First bit of the two bits.
+ *
+ * The Login negotiation requests and responses are associated
+ * with aspecific stage in the session (SecurityNegotiation,
+ * LoginOperationalNegotiation, FullFeaturePhase) and may indicate the
+ * next stage to which they want to move.
+ */
+#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_SECOND_BIT ((ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FIRST_BIT) + 1)
+
+/// Login request flags: Current Stage (CSG): Bit mask.
+#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_MASK ((1 << (ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FIRST_BIT)) | (1 << (ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_SECOND_BIT)))
+
+/// Login request flags: Extracts the Current Stage (CSG) bits.
+#define ISCSI_LOGIN_RESPONSES_FLAGS_GET_CURRENT_STAGE(x) (((x) & ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_MASK) >> ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FIRST_BIT)
-/// Login response flags: Extracts the Current Stage (CSG) bits.
-#define ISCSI_LOGIN_RESPONSE_FLAGS_GET_CURRENT_STAGE(x) (((x) >> 2) & 3)
/**
* @brief Login response flags: Continue.
@@ -5550,14 +5636,21 @@ typedef struct __attribute__((packed)) iscsi_nop_in_packet {
iscsi_bhs_packet *iscsi_create_packet(); // Allocate and initialize an iSCSI BHS packet
void iscsi_destroy_packet(iscsi_bhs_packet *packet_data); // Free resources allocated by iscsi_create_packet
+
iscsi_bhs_packet *iscsi_append_ahs_packet(iscsi_bhs_packet *packet_data, const uint32_t ahs_len); // Allocate and initialize an iSCSI AHS packet and append to existing data stream
int iscsi_get_ahs_packets(const iscsi_bhs_packet *packet_data); // Counts number of AHS packets in an iSCSI data packet stream
iscsi_ahs_packet *iscsi_get_ahs_packet(const iscsi_bhs_packet *packet_data, const int index); // Retrieves the pointer to an specific AHS packet by index
+
+iscsi_bhs_packet *iscsi_append_header_digest_packet(iscsi_bhs_packet *packet_data, const int header_digest_size); // Allocate and initialize an iSCSI header digest (CRC32C) and appends it to existing data stream
+
iscsi_bhs_packet *iscsi_append_ds_packet(iscsi_bhs_packet *packet_data, const int header_digest_size, const uint32_t ds_len, const int data_digest_size); // Allocate and initialize an iSCSI DS packet and append to existing data stream
+
void iscsi_calc_header_digest(const iscsi_bhs_packet *packet_data); // Calculate and store iSCSI header digest (CRC32C)
int iscsi_validate_header_digest(const iscsi_bhs_packet *packet_data); // Validates a stored iSCSI header digest (CRC32C) with actual header data
+
void iscsi_calc_data_digest(const iscsi_bhs_packet *packet_data, const int header_digest_size); // Calculate iSCSI data digest (CRC32C)
int iscsi_validate_data_digest(const iscsi_bhs_packet *packet_data, const int header_digest_size); // Validates a stored iSCSI data digest (CRC32C) with actual DataSegment
+
int iscsi_validate_packet(const struct iscsi_bhs_packet *packet_data, const uint32_t len, const int header_digest_size, const int data_digest_size); // Check if valid iSCSI packet and validate if necessarily
@@ -5630,10 +5723,403 @@ typedef struct iscsi_key_value_pair_packet {
uint len;
} iscsi_key_value_pair_packet;
-int iscsi_parse_key_value_pairs(iscsi_hashmap *pairs, const uint8_t *packet_data, uint len, int cbit, uint8_t **partial_pair); // Extracts all text key / value pairs out of an iSCSI packet into a hash map
+int iscsi_parse_key_value_pairs(iscsi_hashmap *pairs, const uint8_t *packet_data, uint len, int c_bit, uint8_t **partial_pairs); // Extracts all text key / value pairs out of an iSCSI packet into a hash map
int iscsi_create_key_value_pair_packet_callback(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data); // Creates a single partial iSCSI packet stream out of a single text key and value pair
iscsi_key_value_pair_packet *iscsi_create_key_value_pairs_packet(const iscsi_hashmap *pairs); // Creates a properly aligned iSCSI packet DataSegment out of a hash map containing text key and value pairs
+
+/**
+ * @brief iSCSI portal group: Private portal group if set, public otherwise.
+ *
+ * When redirecting logins, there are two portal group types: public and
+ * private.\n
+ * Public portal groups return their portals during discovery session.
+ * A redirection private portal may also be specified for non-discovery
+ * logins.\n
+ * Private portal groups instead do not return their portals during
+ * the discovery session.
+ */
+#define ISCSI_PORTAL_GROUP_PRIVATE (1 << 0)
+
+/// iSCSI portal group: CHAP authentication is disabled.
+#define ISCSI_PORTAL_GROUP_CHAP_DISABLE (1 << 1)
+
+/// iSCSI portal group: CHAP authentication is required.
+#define ISCSI_PORTAL_GROUP_CHAP_REQUIRE (1 << 2)
+
+/// iSCSI portal group: Mutual.
+#define ISCSI_PORTAL_GROUP_CHAP_MUTUAL (1 << 3)
+
+
+/**
+ * @brief This is the main global iSCSI structure which manages all global data.
+ *
+ * All iSCSI portal groups, target nodes, sessions and
+ * connections are stored here for global access.
+ */
+typedef struct iscsi_globals {
+ /// Hash map containing all registered iSCSI portal groups.
+ iscsi_hashmap *portal_groups;
+
+ /// iSCSI target nodes.
+ iscsi_hashmap *target_nodes;
+
+ /// Hash map containing all iSCSI sessions.
+ iscsi_hashmap *sessions;
+
+ /// Hash map containing connections not associated with an iSCSI sessions.
+ iscsi_hashmap *connections;
+} iscsi_globals;
+
+
+/**
+ * @brief iSCSI portal group.
+ *
+ * Portal groups are either public or private and also are used
+ * by CHAP authentication.
+ */
+typedef struct iscsi_portal_group {
+ /// Hash map containing all portals associated with this iSCSI group.
+ iscsi_hashmap *portals;
+
+ /// Reference count.
+ int ref_count;
+
+ /// Tag value for this portal group.
+ int tag;
+
+ /// Portal group flags.
+ int flags;
+
+ /// CHAP group id.
+ int32_t chap_group;
+} iscsi_portal_group;
+
+
+/**
+ * @brief iSCSI portal.
+ *
+ * iSCSI portals manage the host / IP address and port, as well
+ * as the associated connections.
+ */
+typedef struct iscsi_portal {
+ /// Group this portal belongs to.
+ iscsi_portal_group *group;
+
+ /// Hostname / IP address of the portal.
+ uint8_t *host;
+
+ /// Port of the portal.
+ uint8_t *port;
+
+ /// TCP/IP socket for the portal.
+ int sock;
+} iscsi_portal;
+
+
+iscsi_portal_group *iscsi_portal_group_create(const int tag, const int flags); // Creates and initializes an iSCSI portal group
+void iscsi_portal_group_destroy(iscsi_portal_group *portal_group); // Deallocates resources acquired by iscsi_portal_group_create
+int iscsi_portal_group_add_portal(iscsi_portal_group *portal_group, iscsi_portal *portal); // Adds an iSCSI portal to the iSCSI portal group hash map
+
+iscsi_portal *iscsi_portal_create(const uint8_t *host, const uint8_t *port); // Allocates and initializes an iSCSI portal structure
+void iscsi_portal_destroy(iscsi_portal *portal);
+
+
+/// ISCSI port flags: In use.
+#define ISCSI_PORT_FLAGS_IN_USE (1 << 0)
+
+
+/**
+ * @brief iSCSI port.
+ *
+ * This structure maintains the transport ID,
+ * name, identifiers and index of an ISCSI
+ * port.
+ */
+typedef struct iscsi_port {
+ /// Transport ID.
+ uint8_t *transport_id;
+
+ /// Name.
+ uint8_t *name;
+
+ /// Identifier.
+ uint64_t id;
+
+ /// Flags.
+ int flags;
+
+ /// Index.
+ uint16_t index;
+
+ /// Transport ID length.
+ uint16_t transport_id_len;
+} iscsi_port;
+
+
+iscsi_port *iscsi_port_create(const uint8_t *name, const uint64_t id, const uint16_t index); // Allocates and initializes an iSCSI port
+void iscsi_port_destroy(iscsi_port *port); // Deallocates all resource acquired iscsi_port_create
+
+
+/// iSCSI device flags: Allocated.
+#define ISCSI_DEVICE_FLAGS_ALLOCATED (1 << 0)
+
+/// iSCSI device flags: Removed.
+#define ISCSI_DEVICE_FLAGS_REMOVED (1 << 1)
+
+
+typedef struct iscsi_device {
+ /// Name of device.
+ uint8_t *name;
+
+ /// LUNs associated with this device.
+ iscsi_hashmap *luns;
+
+ /// Ports associated with this device.
+ iscsi_hashmap *ports;
+
+ /// Device identifier.
+ int id;
+
+ /// Flags.
+ int flags;
+
+ /// Number of ports.
+ int num_ports;
+
+ /// Portocol identifier.
+ uint8_t protocol_id;
+} iscsi_device;
+
+
+/// iSCSI target node flags: Header digest.
+#define ISCSI_TARGET_NODE_FLAGS_DIGEST_HEADER (1 << 0)
+
+/// iSCSI target node flags: Data digest.
+#define ISCSI_TARGET_NODE_FLAGS_DIGEST_DATA (1 << 1)
+
+/// iSCSI target node flags: CHAP authentication disabled.
+#define ISCSI_TARGET_NODE_FLAGS_CHAP_DISABLE (1 << 2)
+
+/// iSCSI target node flags: CHAP authentication required.
+#define ISCSI_TARGET_NODE_FLAGS_CHAP_REQUIRE (1 << 3)
+
+/// iSCSI target node flags: CHAP authentication mutual.
+#define ISCSI_TARGET_NODE_FLAGS_CHAP_MUTUAL (1 << 4)
+
+/// iSCSI target node flags: CHAP authentication mutual.
+#define ISCSI_TARGET_NODE_FLAGS_DESTROYED (1 << 5)
+
+
+/**
+ * @brief iSCSI target node.
+ *
+ * This structure maintains the name, alias,
+ * associated device and connection data
+ * for a specific iSCSI target node.
+ */
+typedef struct iscsi_target_node {
+ /// Name of target node.
+ uint8_t *name;
+
+ /// Alias name of target node.
+ uint8_t *alias;
+
+ /// Associated iSCSI device.
+ iscsi_device *device;
+
+ /// Target node number.
+ uint num;
+
+ /// Queue depth.
+ uint queue_depth;
+
+ /// Flags.
+ int flags;
+
+ /// CHAP group ID.
+ int chap_group;
+
+ /// Number of active connections for this target node.
+ uint32_t active_conns;
+} iscsi_target_node;
+
+
+/// iSCSI session: Default maximum number of connections.
+#define ISCSI_SESSION_DEFAULT_MAX_CONNECTIONS 2UL
+
+/// iSCSI session: Default maximum number of outstanding ready to transfers.
+#define ISCSI_SESSION_DEFAULT_MAX_OUTSTANDING_R2T 1UL
+
+/// iSCSI session: Default time to wait in seconds.
+#define ISCSI_SESSION_DEFAULT_TIME_TO_WAIT 2UL
+
+/// iSCSI session: Default time to retain in seconds.
+#define ISCSI_SESSION_DEFAULT_TIME_TO_RETAIN 20UL
+
+/// iSCSI session: First burst length in bytes.
+#define ISCSI_SESSION_DEFAULT_FIRST_BURST_LEN ISCSI_DEFAULT_RECV_DS_LEN
+
+/// iSCSI session: Maximum burst length in bytes.
+#define ISCSI_SESSION_DEFAULT_MAX_BURST_LEN (ISCSI_DEFAULT_MAX_RECV_DS_LEN * ISCSI_DEFAULT_MAX_DATA_OUT_PER_CONNECTION)
+
+/// iSCSI session: Default initial ready to transfer state.
+#define ISCSI_SESSION_DEFAULT_INIT_R2T true
+
+/// iSCSI session: Default immediate data state.
+#define ISCSI_SESSION_DEFAULT_IMMEDIATE_DATA true
+
+/// iSCSI session: Default data PDU in order state.
+#define ISCSI_SESSION_DEFAULT_DATA_PDU_IN_ORDER true
+
+/// iSCSI session: Default data sequence in order state.
+#define ISCSI_SESSION_DEFAULT_DATA_SEQ_IN_ORDER true
+
+/// iSCSI session: Default error recovery level.
+#define ISCSI_SESSION_DEFAULT_ERR_RECOVERY_LEVEL 0L
+
+
+/// iSCSI session type: Invalid.
+#define ISCSI_SESSION_TYPE_INVALID 0L
+
+/// iSCSI session type: Normal.
+#define ISCSI_SESSION_TYPE_NORMAL 1L
+
+/// iSCSI session type: Discovery.
+#define ISCSI_SESSION_TYPE_DISCOVERY 2L
+
+
+/**
+ * @brief iSCSI session.
+ *
+ * This structure manages an iSCSI session and
+ * stores the key / value pairs from the
+ * login phase.
+ */
+typedef struct iscsi_session {
+ /// TCP/IP Connections associated with this session.
+ iscsi_hashmap *connections;
+
+ /// Initiator port.
+ iscsi_port *initiator_port;
+
+ /// Login key / value pairs negotiated with this session.
+ iscsi_hashmap *key_value_pairs;
+
+ /// Portal group tag.
+ int tag;
+
+ /// Initiator Session ID (ISID).
+ uint64_t isid;
+
+ /// Target Session Identifying Handle (TSIH).
+ uint16_t tsih;
+
+ /// iSCSI target node.
+ iscsi_target_node *target;
+
+ /// Queue depth.
+ int queue_depth;
+
+ /// iSCSI session type.
+ int type;
+
+ /// Maximum number of connections.
+ uint max_conns;
+
+ /// Ready to transfer maximum outstanding value.
+ uint max_outstanding_r2t;
+
+ /// Default time to wait.
+ uint default_time_to_wait;
+
+ /// Default time to retain.
+ uint default_time_to_retain;
+
+ /// First burst length.
+ uint first_burst_len;
+
+ /// Maximum burst length.
+ uint max_burst_len;
+
+ /// Initial ready to transfer bit.
+ int init_r2t;
+
+ /// Immediate data bit.
+ int immediate_data;
+
+ /// Data PDU in order bit.
+ int data_pdu_in_order;
+
+ /// Data sequence in order bit.
+ int data_seq_in_order;
+
+ /// Error recovery level.
+ uint err_recovery_level;
+
+ /// ExpCmdSN.
+ uint32_t exp_cmd_sn;
+
+ /// MaxCmdSN.
+ uint32_t max_cmd_sn;
+
+ /// Current text Initiator Task Tag (ITT).
+ uint32_t current_text_init_task_tag;
+} iscsi_session;
+
+
+/// iSCSI connection read packet data return code from iscsi_connection_pdu_read function: Packet parsed successfully.
+#define ISCSI_CONNECT_PDU_READ_OK 0L
+
+/// iSCSI connection read packet data return code from iscsi_connection_pdu_read function: Packet processed successfully.
+#define ISCSI_CONNECT_PDU_READ_PROCESSED 1L
+
+/// iSCSI connection read packet data return code from iscsi_connection_pdu_read function: Fatail error during packet parsing.
+#define ISCSI_CONNECT_PDU_READ_ERR_FATAL -1L
+
+/// iSCSI connection read packet data return code from iscsi_connection_pdu_read function: Login error response.
+#define ISCSI_CONNECT_PDU_READ_ERR_LOGIN_RESPONSE -2L
+
+
+/// iSCSI connection flags: Stopped.
+#define ISCSI_CONNECT_FLAGS_STOPPED (1 << 0L)
+
+/// iSCSI connection flags: Rejected.
+#define ISCSI_CONNECT_FLAGS_REJECTED (1 << 1L)
+
+/// iSCSI connection flags: Logged out.
+#define ISCSI_CONNECT_FLAGS_LOGGED_OUT (1 << 2L)
+
+/// iSCSI connection flags: Full feature.
+#define ISCSI_CONNECT_FLAGS_FULL_FEATURE (1 << 3L)
+
+
+/// Ready to wait for PDU.
+#define ISCSI_CONNECT_PDU_RECV_STATE_WAIT_PDU_READY 0L
+
+/// Active connection waiting for any PDU header.
+#define ISCSI_CONNECT_PDU_RECV_STATE_WAIT_PDU_HDR 1L
+
+/// Active connection waiting for data.
+#define ISCSI_CONNECT_PDU_RECV_STATE_WAIT_PDU_DATA 2L
+
+/// Active connection does not wait for data.
+#define ISCSI_CONNECT_PDU_RECV_STATE_ERR 3L
+
+
+/// iSCSI connection state: Invalid.
+#define ISCSI_CONNECT_STATE_INVALID 0L
+
+/// iSCSI connection state: Running.
+#define ISCSI_CONNECT_STATE_RUNNING 1L
+
+/// iSCSI connection state: Exiting.
+#define ISCSI_CONNECT_STATE_EXITING 2L
+
+/// iSCSI connection state: Invalid.
+#define ISCSI_CONNECT_STATE_EXITED 3L
+
+
/**
* @brief iSCSI incoming connection.
*
@@ -5643,16 +6129,44 @@ iscsi_key_value_pair_packet *iscsi_create_key_value_pairs_packet(const iscsi_has
* and iSCSI portals.
*/
typedef struct iscsi_connection {
+ /// iSCSI session associated with this connection.
+ iscsi_session *session;
+
/// Hash map containing text key / value pairs associated to this connection.
iscsi_hashmap *key_value_pairs;
+ /// Temporarily storage for partially received parameter.
+ uint8_t *partial_pairs;
+
/// iSCSI connection contains a header digest (CRC32), always MUST be 0 or 4 for now.
int header_digest;
/// iSCSI connection contains a data digest (CRC32), always MUST be 0 or 4 for now.
int data_digest;
- int8_t flags;
+ /// Current PDU being processed.
+ struct iscsi_pdu *pdu_processing;
+
+ /// Login response PDU.
+ struct iscsi_pdu *login_response_pdu;
+
+ /// Connected TCP/IP socket.
+ int sock;
+
+ /// iSCSI connection receiving state.
+ int pdu_recv_state;
+
+ /// iSCSI connection flags.
+ int flags;
+
+ /// iSCSI connection state.
+ int state;
+
+ /// Maximum receive DataSegment length in bytes.
+ int max_recv_ds_len;
+
+ /// Portal group tag.
+ int pg_tag;
/// Initiator Session ID (ISID).
iscsi_isid isid;
@@ -5666,15 +6180,89 @@ typedef struct iscsi_connection {
/// Connection ID (CID).
uint16_t cid;
- /// CmdSN.
- uint32_t cmd_sn;
+ /// StatSN.
+ uint32_t stat_sn;
/// ExpStatSN.
uint32_t exp_stat_sn;
} iscsi_connection;
-iscsi_connection *iscsi_connection_create(const iscsi_login_req_packet *login_req_pkt); // Creates data structure for an iSCSI connection request
-void iscsi_connection_destroy(iscsi_connection *conn); // Deallocates all resources acquired by iscsi_connection_create
+iscsi_connection *iscsi_connection_create(iscsi_portal *portal, const int sock); // Creates data structure for an iSCSI connection from iSCSI portal and TCP/IP socket
int iscsi_connection_destroy_callback(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data); // iSCSI connection destructor callback for hash map
+void iscsi_connection_destroy(iscsi_connection *conn); // Deallocates all resources acquired by iscsi_connection_create
+
+
+/// iSCSI PDU flags: Rejected.
+#define ISCSI_PDU_FLAGS_REJECTED (1 << 0)
+
+
+/**
+ * @brief This structure is used to partially read PDU data.
+ *
+ * Since TCP/IP packets can be fragmented, this
+ * structure is needed which maintains reading
+ * and filling the BHS, AHS and DS properly.
+ */
+typedef struct iscsi_pdu {
+ /// iSCSI Basic Header Segment (BHS) packet data.
+ iscsi_bhs_packet *bhs_pkt;
+
+ /// iSCSI Advanced Header Segment (AHS) packet data for fast access and is straight after BHS packet in memory.
+ iscsi_ahs_packet *ahs_pkt;
+
+ /// Header digest (CRC32C) packet data for fast access and is straight after BHS and AHS packet in memory.
+ iscsi_header_digest *header_digest;
+
+ /// iSCSI DataSegment (DS) packet data for fast access and is straight after BHS, AHS and header digest packet in memory.
+ iscsi_ds_cmd_data *ds_cmd_data;
+
+ /// Data digest (CRC32C) packet data for fast access and is straight after BHS, AHS, header digest and DataSegment packet in memory.
+ iscsi_data_digest *data_digest;
+
+ /// Flags.
+ int flags;
+
+ /// Header digest size (always 0 or 4 for now).
+ int header_digest_size;
+
+ /// Bytes of header digest (CRC32C) already read.
+ uint header_digest_read_len;
+
+ /// Data digest size (always 0 or 4 for now).
+ int data_digest_size;
+
+ /// Bytes of data digest (CRC32C) already read.
+ uint data_digest_read_len;
+
+ /// Bytes of Basic Header Segment (BHS) already read.
+ uint bhs_read_len;
+
+ /// AHSLength.
+ uint ahs_len;
+
+ /// Bytes of Advanced Header Segment (AHS) already read.
+ uint ahs_read_len;
+
+ /// DataSegmentLength.
+ uint ds_len;
+
+ /// Remaining bytes of DataSegment buffer to read.
+ uint buf_len;
+
+ /// Associated connection.
+ iscsi_connection *conn;
+
+ /// CmdSN.
+ uint32_t cmd_sn;
+} iscsi_pdu;
+
+typedef void (*iscsi_connection_xfer_complete_callback)(uint8_t *user_data); // iSCSI transfer completed callback function.
+
+iscsi_pdu *iscsi_connection_pdu_create(iscsi_connection *conn); // Creates an iSCSI PDU structure used by connections
+void iscsi_connection_pdu_destroy(iscsi_pdu *pdu); // Destroys an iSCSI PDU structure used by connections
+
+int iscsi_connection_read_data(struct spdk_iscsi_conn *conn, int len, void *buf);
+int iscsi_connection_readv_data(struct spdk_iscsi_conn *conn, struct iovec *iov, int iovcnt);
+void iscsi_connection_pdu_write(iscsi_connection *conn, iscsi_pdu *pdu, iscsi_connection_xfer_complete_callback callback, uint8_t *user_data);
#endif /* DNBD3_ISCSI_H_ */