From c1be00f5adc0c98028456b11d4e9331f5b8476c0 Mon Sep 17 00:00:00 2001 From: Sebastian Vater Date: Thu, 7 Aug 2025 17:13:16 +0200 Subject: 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. --- src/server/iscsi.c | 1651 ++++++++++++++++++++++++++++++++++++++++++++++++++-- src/server/iscsi.h | 648 ++++++++++++++++++++- 2 files changed, 2205 insertions(+), 94 deletions(-) (limited to 'src') 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 #include #include +#include #include #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; } @@ -967,6 +968,54 @@ iscsi_ahs_packet *iscsi_get_ahs_packet(const iscsi_bhs_packet *packet_data, cons return NULL; } +/** + * @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. * @@ -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,87 +1858,128 @@ iscsi_key_value_pair_packet *iscsi_create_key_value_pairs_packet(const iscsi_has } /** - * @brief Creates data structure for an iSCSI connection request. - * - * Creates a data structure for an incoming iSCSI connection request - * from iSCSI packet data. - * - * @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. - * @return Pointer to initialized iSCSI connection structure or NULL in - * case of an error (invalid iSCSI packet data or memory exhaustion). + * @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). */ -iscsi_connection *iscsi_connection_create(const iscsi_login_req_packet *login_req_pkt) +static int iscsi_add_int_key_value_pair(iscsi_hashmap *key_value_pairs, const uint8_t *key, const int value) { - iscsi_connection *conn = (iscsi_connection *) malloc( sizeof(struct iscsi_connection) ); + const uint key_len = strlen( key ) + 1; + uint8_t *hash_key = iscsi_hashmap_key_create( key, key_len ); - if ( conn == NULL ) { - logadd( LOG_ERROR, "iscsi_create_connection: Out of memory while allocating iSCSI connection" ); + if ( hash_key == NULL ) { + logadd( LOG_ERROR, "iscsi_add_int_key_value_pair: Out of memory allocating key." ); - return NULL; + return -1L; } - conn->key_value_pairs = iscsi_hashmap_create( 32UL ); + uint8_t *hash_val = iscsi_sprintf_alloc( "%d", value ); - if ( conn->key_value_pairs == NULL ) { - logadd( LOG_ERROR, "iscsi_create_connection: Out of memory while allocating iSCSI text key / value pair hash map" ); + if ( hash_val == NULL ) { + logadd( LOG_ERROR, "iscsi_add_int_key_value_pair: Out of memory allocating integer value." ); - free( conn ); + return -1L; + } - return NULL; + 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; } - conn->header_digest = 0L; - conn->data_digest = 0L; + uint8_t *bool_val = (value ? "Yes" : "No"); + uint8_t *hash_val = (uint8_t *) malloc( strlen( bool_val ) + 1 ); - 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); + if ( hash_val == NULL ) { + logadd( LOG_ERROR, "iscsi_add_bool_key_value_pair: Out of memory allocating boolean string value" ); - 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); + return -1L; + } - return conn; + strcpy( hash_val, bool_val ); + + return iscsi_hashmap_put( key_value_pairs, hash_key, key_len, hash_val ); } /** - * @brief Deallocates all resources acquired by iscsi_connection_create. - * - * Deallocates a data structure of an iSCSI connection - * request and all allocated hash maps which don't - * require closing of external resources like closing - * TCP/IP socket connections. - * - * @param[in] conn Pointer to iSCSI connection structure to be - * deallocated, TCP/IP connections are NOT closed by this - * function, use iscsi_connection_close for this. This may be - * NULL in which case this function does nothing. + * @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 */ -void iscsi_connection_destroy(iscsi_connection *conn) +iscsi_portal_group *iscsi_portal_group_create(const int tag, const int flags) { - if ( conn != 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 ); + iscsi_portal_group *portal_group = (iscsi_portal_group *) malloc( sizeof(struct iscsi_portal_group) ); - conn->key_value_pairs = NULL; - } + if ( portal_group == NULL ) { + logadd( LOG_ERROR, "iscsi_portal_group_create: Out of memory allocating iSCSI portal group structure" ); - free( conn ); + 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 connection destructor callback for hash map. + * @brief iSCSI portal destructor callback for hash map. * * Callback function for deallocation of an iSCSI - * connection stored in the hash map managing all - * iSCSI connections. + * 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. @@ -1902,10 +1992,1443 @@ void iscsi_connection_destroy(iscsi_connection *conn) * 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) +int iscsi_portal_destroy_callback(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data) { - iscsi_connection_destroy( (iscsi_connection *) value ); + 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. + * + * This function also frees the associated key and value pairs, + * the attached connections as well as frees the initator port. + * + * @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(iscsi_portal *portal, const int sock) +{ + iscsi_connection *conn = (iscsi_connection *) malloc( sizeof(struct iscsi_connection) ); + + if ( conn == NULL ) { + logadd( LOG_ERROR, "iscsi_create_connection: Out of memory while allocating iSCSI connection" ); + + return NULL; + } + + conn->session = NULL; + conn->key_value_pairs = iscsi_hashmap_create( 32UL ); + + if ( conn->key_value_pairs == NULL ) { + logadd( LOG_ERROR, "iscsi_create_connection: Out of memory while allocating iSCSI text key / value pair hash map" ); + + free( conn ); + + return NULL; + } + + 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; + + return conn; +} + +/** + * @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 0L; +} + +/** + * @brief Deallocates all resources acquired by iscsi_connection_create. + * + * Deallocates a data structure of an iSCSI connection + * request and all allocated hash maps which don't + * require closing of external resources like closing + * TCP/IP socket connections. + * + * @param[in] conn Pointer to iSCSI connection structure to be + * deallocated, TCP/IP connections are NOT closed by this + * function, use iscsi_connection_close for this. This may be + * NULL in which case this function does nothing. + */ +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 ); + + conn->key_value_pairs = NULL; + } + + free( conn ); + } +} + +/** + * @brief Reads data for the specified iSCSI connection from its TCP socket. + * + * The TCP socket is marked as non-blocking, so this function may not read + * all data requested. + * + * Returns SPDK_ISCSI_CONNECTION_FATAL if the recv() operation indicates a fatal + * error with the TCP connection (including if the TCP connection was closed + * unexpectedly. + * + * Otherwise returns the number of bytes successfully read. + */ +int iscsi_connection_read(const iscsi_connection *conn, uint8_t *buf, const uint len) +{ + 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_ */ -- cgit v1.2.3-55-g7522