diff options
| author | Sebastian Vater | 2025-07-07 15:47:48 +0200 |
|---|---|---|
| committer | Simon Rettberg | 2025-12-09 15:33:20 +0100 |
| commit | 65f8d369f445e7ffcd0a6995fe0b9a6528308b38 (patch) | |
| tree | 6b00765dda789ffa98352d347cbea2c37cf0af30 | |
| parent | github: add Rocky 10.0 Kernel from pub (switch to vault when exists) (diff) | |
| download | dnbd3-65f8d369f445e7ffcd0a6995fe0b9a6528308b38.tar.gz dnbd3-65f8d369f445e7ffcd0a6995fe0b9a6528308b38.tar.xz dnbd3-65f8d369f445e7ffcd0a6995fe0b9a6528308b38.zip | |
[SERVER] iscsi: Initial commit, WIP
| -rw-r--r-- | src/server/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/server/iscsi.c | 1631 | ||||
| -rw-r--r-- | src/server/iscsi.h | 2956 |
3 files changed, 4589 insertions, 0 deletions
diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 9a1e1c4..c66ace4 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -49,6 +49,7 @@ set(DNBD3_SERVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/altservers.c ${CMAKE_CURRENT_SOURCE_DIR}/image.c ${CMAKE_CURRENT_SOURCE_DIR}/ini.c ${CMAKE_CURRENT_SOURCE_DIR}/integrity.c + ${CMAKE_CURRENT_SOURCE_DIR}/iscsi.c ${CMAKE_CURRENT_SOURCE_DIR}/locks.c ${CMAKE_CURRENT_SOURCE_DIR}/net.c ${CMAKE_CURRENT_SOURCE_DIR}/reference.c @@ -65,6 +66,7 @@ set(DNBD3_SERVER_HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/altservers.h ${CMAKE_CURRENT_SOURCE_DIR}/image.h ${CMAKE_CURRENT_SOURCE_DIR}/ini.h ${CMAKE_CURRENT_SOURCE_DIR}/integrity.h + ${CMAKE_CURRENT_SOURCE_DIR}/iscsi.h ${CMAKE_CURRENT_SOURCE_DIR}/locks.h ${CMAKE_CURRENT_SOURCE_DIR}/net.h ${CMAKE_CURRENT_SOURCE_DIR}/reference.h diff --git a/src/server/iscsi.c b/src/server/iscsi.c new file mode 100644 index 0000000..2dc85cc --- /dev/null +++ b/src/server/iscsi.c @@ -0,0 +1,1631 @@ + /* + * This file is part of the Distributed Network Block Device 3 + * + * Copyright(c) 2025 Sebastian Vater <sebastian.vater@rz.uni-freiburg.de> + * + * This file may be licensed under the terms of the + * GNU General Public License Version 2 (the ``GPL''). + * + * Software distributed under the License is distributed + * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the GPL for the specific language + * governing rights and limitations. + * + * You should have received a copy of the GPL along with this + * program. If not, go to http://www.gnu.org/licenses/gpl.html + * or write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <dnbd3/shared/log.h> +#include "iscsi.h" + +/** + * @file iscsi.c + * @author Sebastian Vater + * @date 16 Jul 2025 + * @brief iSCSI implementation for DNBD3. + * + * This file contains the iSCSI implementation according to + * RFC7143 for dnbd3-server. + * All server-side network sending and client-side network + * receiving code is done here. + * @see https://www.rfc-editor.org/rfc/rfc7143 + */ + +/** + * Merges multiple strings using printf style formatting + * and allocates memory for holding the result. + * + * @param[in] buf Buffer to merge into. + * @param[in] format printf style format string. + * @param[in] args Values to fill in the format with. + * @return New buffer which holds the final result. + */ +uint8_t *iscsi_vsprintf_append_realloc(char *buf, const char *format, va_list args) +{ + va_list args_copy; + uint orig_size = 0; + + if ( buf != NULL ) + orig_size = strlen( (char *) buf ); + + va_copy( args_copy, args ); + uint new_size = vsnprintf( NULL, 0, format, args_copy ); + va_end( args_copy ); + + new_size += orig_size + 1; + + uint8_t *new_buf = realloc( buf, new_size ); + + if ( new_buf == NULL ) { + logadd( LOG_ERROR, "iscsi_vsprintf_append_realloc: Out of memory while allocating string buffer" ); + + return NULL; + } + + vsnprintf( (char *) new_buf + orig_size, (new_size - orig_size), format, args ); + + return new_buf; +} + +/** + * Merges strings using printf style formatting and allocates + * memory for holding the result. + * + * @param[in] buf Buffer to merge into. + * @param[in] format printf style format string. + * @param[in] ... Values to fill in the format string with. + * @return New buffer which holds the final result. + */ +uint8_t *iscsi_sprintf_append_realloc(char *buf, const char *format, ...) +{ + va_list args; + + va_start( args, format ); + uint8_t *ret_buf = iscsi_vsprintf_append_realloc( buf, format, args ); + va_end( args ); + + return ret_buf; +} + +/** + * Merges strings using printf style formatting and allocates + * memory for holding the result. + * + * @param[in] format printf style format string. + * @param[in] args Values to fill in the format with. + * @return New buffer which holds the final result. + */ +uint8_t *iscsi_vsprintf_alloc(const char *format, va_list args) +{ + return iscsi_vsprintf_append_realloc( NULL, format, args ); +} + +/** + * Allocates a buffer large enough to hold printf style + * string concatenation and fills it using vspnrintf. + * + * @param[in] format Format string to allocate and fill. + * @param[in] ... Values to fill in the format string with. + * @return New buffer containing the formatted string. + */ +uint8_t *iscsi_sprintf_alloc(const char *format, ... ) +{ + va_list args; + + va_start( args, format ); + uint8_t *buf = iscsi_vsprintf_alloc( format, args ); + va_end( args ); + + return buf; +} + +/** + * Creates a ultra hardcore speed optimized empty hash map and + * allocates enough buckets to hold default capacity elements. + * The speed optimizations require all keys having a size of + * a multiple of 8 bytes with zero padding. Also the capacity + * always nas to be a power of two. + * TODO: Move all hash map related functions to different source file + * later and implement in a lock-free way for better concurrency. + * + * @param[in] capacity Desired initial capacity, will be rounded up + * to the nearest power of two. If set to 0, a default capacity of + * 32 buckets will be used instead. + * @return A pointer to the hash map structure or NULL in case of an error. + */ +iscsi_hashmap *iscsi_hashmap_create(const uint capacity) +{ + iscsi_hashmap *map = (iscsi_hashmap *) malloc( sizeof(iscsi_hashmap) ); + + if ( map == NULL ) { + logadd( LOG_ERROR, "iscsi_hashmap_create: Out of memory while allocating iSCSI hash map" ); + + return map; + } + + if ( capacity > 0UL ) { + uint new_capacity = (capacity + 1); // 1UL << (lg(capacity - 1) + 1) + + new_capacity |= (new_capacity >> 1UL); + new_capacity |= (new_capacity >> 2UL); + new_capacity |= (new_capacity >> 4UL); + new_capacity |= (new_capacity >> 8UL); + new_capacity |= (new_capacity >> 16UL); + + map->capacity = ++new_capacity; // Round up actual new capacity to nearest power of two + } else { + map->capacity = ISCSI_HASHMAP_DEFAULT_CAPACITY; + } + + map->buckets = (iscsi_hashmap_bucket *) calloc( map->capacity, sizeof(struct iscsi_hashmap_bucket) ); + + if ( map->buckets == NULL ) { + free( map ); + + logadd( LOG_ERROR, "iscsi_hashmap_create: Out of memory while allocating iSCSI hash map buckets" ); + + return NULL; + } + + map->cap_load = (map->capacity * 3UL) >> 2UL; // 75% of capacity + map->count = 0; + map->removed_count = 0; + map->first = NULL; + map->last = (iscsi_hashmap_bucket *) &map->first; + + return map; +} + +/** + * Deallocates all buckets and the hash map itself allocated + * by iscsi_hashmap_create. The elements associated with the + * buckets are NOT freed by this function, this has to be done + * either manually or using the function iscsi_hashmap_iterate. + * + * @param[in] map Pointer to hash map and its buckets to deallocate. + * If this is NULL, nothing is done. + */ +void iscsi_hashmap_destroy(iscsi_hashmap *map) +{ + if ( map != NULL ) { + if ( map->buckets != NULL ) + free( map->buckets ); + + free( map ); + } +} + +/** + * Puts an old bucket into a resized hash map. + * + * @param[in] map Pointer to resized hash map, may NOT be NULL, so + * be careful. + * @param[in] old_entry The old bucket to be put into the resized + * hash map. + * @return New bucket where the bucket has been put into. + */ +static iscsi_hashmap_bucket *iscsi_hashmap_resize_entry(iscsi_hashmap *map, const iscsi_hashmap_bucket *old_entry) +{ + uint32_t index = old_entry->hash & (map->capacity - 1); + + for ( ;; ) { + iscsi_hashmap_bucket *entry = &map->buckets[index]; + + if ( entry->key == NULL ) { + *entry = *old_entry; + + return entry; + } + + index = (index + 1) & (map->capacity - 1); + } +} + +/** + * Resizes a hash map by doubling its bucket capacity. if any + * buckets have been removed, they are finally purged. The + * old bucket list is freed after the resize operation has + * been finished. + * + * @param[in] map Pointer to hash map to resize. This may NOT be + * NULL, so be careful. + * @retval -1 An error occured during resize. + * @retval 0 Hash map has been resized successfully. + */ +static int iscsi_hashmap_resize(iscsi_hashmap *map) +{ + const uint old_capacity = map->capacity; + iscsi_hashmap_bucket *old_buckets = map->buckets; + + map->capacity <<= ISCSI_HASHMAP_RESIZE_SHIFT; + + map->buckets = (iscsi_hashmap_bucket *) calloc( map->capacity, sizeof(struct iscsi_hashmap_bucket) ); + + if ( map->buckets == NULL ) { + map->capacity = old_capacity; + map->buckets = old_buckets; + + return -1; + } + + map->cap_load = (map->capacity * 3UL) >> 2UL; // 75% of capacity + map->last = (iscsi_hashmap_bucket *) &map->first; + map->count -= map->removed_count; + map->removed_count = 0; + + do { + iscsi_hashmap_bucket *current = map->last->next; + + if ( current->key == NULL ) { + map->last->next = current->next; + + continue; + } + + map->last->next = iscsi_hashmap_resize_entry(map, map->last->next); + map->last = map->last->next; + } while ( map->last->next != NULL ); + + free( old_buckets ); + + return 0; +} + +/** + * Calculates the hash code of data with a specified length. + * + * @param[in] data Pointer to data to be hashed, NULL is NOT + * an allowed here, so be careful. Data needs 8 byte alignment + * and needs to be zero padded. + * @param[in] len Number of bytes of hash data, must be larger + * than 0 and is rounded up to the nearest 8 byte integer prior + * calculating the hash code, so be careful. + * @return Hash code of data. + */ +static inline uint32_t iscsi_hashmap_hash_data(const uint8_t *data, const size_t len) +{ + const uint64_t *hash_data = (const uint64_t *) data; + size_t num_blocks = iscsi_align(len, ISCSI_HASHMAP_KEY_ALIGN) >> ISCSI_HASHMAP_KEY_ALIGN_SHIFT; + uint64_t hash = ISCSI_HASHMAP_HASH_INITIAL; + + do { + hash ^= *hash_data++; + hash *= ISCSI_HASHMAP_HASH_MUL; + } while ( --num_blocks > 0UL ); + + return (uint32_t) (hash ^ hash >> 32ULL); +} + +/** + * Finds a bucket by key of a specified hash map by + * key, key size and hash code. This function may + * only be called if the bucket is guaranteed to + * be found, otherwise this function hangs, so be + * careful. + * + * @param[in] map Pointer to hash map where the key to be + * searched for is located, may NOT be NULL, so be careful. + * @param[in] key Pointer to key. NULL is invalid, so be + * careful. + * @param[in] key_size Number of bytes for the key. + * @param[in] hash Hash of the key to be searched for. + * @return Pointer to found bucket. + */ +static iscsi_hashmap_bucket *iscsi_hashmap_find_entry(iscsi_hashmap *map, const uint8_t *key, size_t key_size, uint32_t hash) +{ + uint32_t index = hash & (map->capacity - 1); + + for ( ;; ) { + iscsi_hashmap_bucket *entry = &map->buckets[index]; + + if ( (entry->key == NULL && entry->value == NULL) || (entry->key != NULL && entry->key_size == key_size && entry->hash == hash && (memcmp( entry->key, key, key_size ) == 0)) ) + return entry; + + index = (index + 1) & (map->capacity - 1); + } +} + +/** + * Creates a key from data and size and ensures + * its requirements for usage in hash map buckets. + * Currently keys to be used in a hash map bucket + * require a size of multiple by 8 bytes with + * the zero padding. + * + * @param[in] data Pointer to data to construct the key + * from and may NOT be NULL, so be careful. + * @param[in] len Length of the data to construct the key + * from, MUST be larger than 0, so be careful. + * @return Pointer to generated usable key or NULL in + * case of an error (usually memory exhaustion). + */ +uint8_t *iscsi_hashmap_key_create(const uint8_t *data, const size_t len) +{ + const size_t key_size = iscsi_align(len, ISCSI_HASHMAP_KEY_ALIGN); + uint8_t *key = (uint8_t *) malloc( key_size ); + + if ( key == NULL ) { + logadd( LOG_ERROR, "iscsi_hashmap_key_create: Out of memory while allocating iSCSI hash map key" ); + + return key; + } + + memcpy( key, data, len ); + memset( key + len, 0, (key_size - len) ); // Zero pad additional bytes in case length is not a multiple of 8 + + return key; +} + +/** + * Deallocates a key allocated with the function + * iscsi_hashmap_key_create. + * + * @param[in] key Pointer to key to deallocate, may NOT + * be NULL, so be careful. + */ +void iscsi_hashmap_key_destroy(uint8_t *key) { + free( key ); +} + +/** + * Default callback function for deallocation of + * allocated hash map resources by simply calling + * free. + * + * @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. + * @return Always returns 0 as this function cannot fail. + */ +int iscsi_hashmap_key_destroy_value_callback(uint8_t *key, const size_t key_size, uint8_t *value, uint8_t *user_data) +{ + if ( value != NULL ) + free( value ); + + iscsi_hashmap_key_destroy( key ); + + return 0L; +} + +/** + * Adds a key / value pair to a specified hash map + * bucket list, if it doesn't exist already. The + * buckets are resized automatically if required. + * This function neither does make a copy of the key, + * nor of the value. Keys should be allocated using + * the function iscsi_hashmap_key_create or freed by + * using iscsi_hashmap_key_destroy in order to + * ensure the alignment and padding requirements. + * + * @param[in] map Pointer to hash map where the key and + * value pair should be added to, may NOT be NULL, so + * be careful. + * @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 to add, NULL is + * allowed. + * @retval -1 Adding key / value pair would have required + * hash map resizing which failed (probably due to + * memory exhaustion). + * @retval 0 Key / value pair was added successfully. + */ +int iscsi_hashmap_put(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t *value) +{ + if ( ((map->count + 1) > map->cap_load) && (iscsi_hashmap_resize( map ) < 0) ) + return -1; + + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + if ( entry->key == NULL ) { + map->last->next = entry; + map->last = entry; + entry->next = NULL; + + map->count++; + + entry->key = key; + entry->key_size = key_size; + entry->hash = hash; + } + + entry->value = value; + + return 0; +} + +/** + * Adds a key / value pair if it doesn't exist + * using the value of `*out_in_val`. If the pair + * does exist, value will be set in `*out_in`, + * meaning the value of the pair will be in + * '*out_in` regardless of whether or not it it + * existed in the first place. + * The buckets are resized automatically if required. + * This function neither does make a copy of the key, + * nor of the value. Keys should be allocated using + * the function iscsi_hashmap_key_create or freed by + * using iscsi_hashmap_key_destroy in order to + * ensure the alignment and padding requirements. + * + * @param[in] map Pointer to hash map where the key and + * value pair should be added to, may NOT be NULL, so + * be careful. + * @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,out] out_in_value Value of the key to add, + * NULL is allowed. + * @retval -1 Adding key / value pair would have required + * hash map resizing which failed (probably due to + * memory exhaustion). + * @retval 0 Key / value pair was added successfully. + * @retval 1 Key already existed. + */ +int iscsi_hashmap_get_put(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t **out_in_value) +{ + if ( ((map->count + 1) > map->cap_load) && (iscsi_hashmap_resize( map ) < 0) ) + return -1; + + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + if ( entry->key == NULL ) { + map->last->next = entry; + map->last = entry; + entry->next = NULL; + + map->count++; + + entry->value = *out_in_value; + entry->key = key; + entry->key_size = key_size; + entry->hash = hash; + + return 0; + } + + *out_in_value = entry->value; + + return 1; +} + +/** + * Adds a key / value pair to a specified hash map + * bucket list. If the key already exists, it will + * be overwritten and a callback function will be + * invoked in order to allow, e.g. deallocation of + * resources, if necessary. The buckets are resized + * automatically if required. This function neither + * does make a copy of the key, nor of the value. + * Keys should be allocated using the function + * iscsi_hashmap_key_create or freed by using + * iscsi_hashmap_key_destroy in order to ensure the + * alignment and padding requirements. + * + * @param[in] map Pointer to hash map where the key and + * value pair should be added to, may NOT be NULL, so + * be careful. + * @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 to add, NULL is + * allowed. + * @param[in] callback Callback function which allows, + * for example, a dallocation of resources for the + * overwritten key and value pair. The function is + * invoked just before overwriting the old values. + * This may NOT be NULL, so take caution. + * @param[in] user_data Pointer to user specific data + * passed to the callback function in case more + * information is needed. + * @return -1 in case adding key / value pair would + * have required hash map resizing which failed + * (probably due to memory exhaustion), 0 if the + * Key / value pair was added successfully and + * the callback function also returned 0, otherwise + * the return value got by the callbuck function. + */ +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) +{ + if ( ((map->count + 1) > map->cap_load) && (iscsi_hashmap_resize( map ) < 0) ) + return -1; + + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + if ( entry->key == NULL ) { + map->last->next = entry; + map->last = entry; + entry->next = NULL; + + map->count++; + + entry->key = key; + entry->key_size = key_size; + entry->hash = hash; + entry->value = value; + + return 0; + } + + int err = callback( entry->key, key_size, entry->value, user_data ); + + entry->key = key; + entry->value = value; + + return err; +} + +/** + * Checks whether a specified key exists in a hash map. + * + * @param[in] map Pointer to the hash map to be searched + * for the key to check for existence and may not be + * NULL, so take caution. + * @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. + * @retval TRUE The key exists. + * @retval FALSE The key does not exist. + */ +int iscsi_hashmap_contains(iscsi_hashmap *map, const uint8_t *key, const size_t key_size) +{ + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + return entry->key != NULL; +} + +/** + * Retrieves the value of a specified key from a hash map. Since the + * hash map supports NULL values, it is stored in an output variable. + * + * @param[in] map Pointer to the hash map to be searched + * for the key of which the value should be retrieved and + * may not be NULL, so take caution. + * @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[out] out_value Pointer where the value of the found key + * is stored, maybe NULL if either the key's value is NULL or + * in case the key was not found. The pointer to the value itself + * may NOT be NULL, so be careful. + * @retval TRUE The key has been found and its value stored + * in the 'out_value' parameter. + * @retval FALSE The key has not been found and NULL has been + * stored in the 'out_value' parameter. + */ +int iscsi_hashmap_get(iscsi_hashmap *map, const uint8_t *key, const size_t key_size, uint8_t **out_value) +{ + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + *out_value = entry->value; + + return entry->key != NULL; +} + +/** + * Removes an element from the bucket list of the hash map. + * Buckets are marked as removed by setting their key and + * value to NULL. The actual removal will be done upon next + * resize operation. If the specified key already has been + * removed, this function will do nothing. + * + * @param[in] map Pointer to the hash map to remove from + * and may not be NULL, so take caution. + * @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. + */ +void iscsi_hashmap_remove(iscsi_hashmap *map, const uint8_t *key, const size_t key_size) +{ + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + if ( entry->key != NULL ) { + entry->key = NULL; + entry->value = NULL; + + map->removed_count++; + } +} + +/** + * Removes an element from the bucket list of the hash map. + * Buckets are marked as removed by setting their key and + * value to NULL. The actual removal will be done upon next + * resize operation. A callback function is invoked if the + * key to be removed is found in the bucket list and allows, + * e.g. to free any resources associated with the key. If + * the key is not found, this function will do nothing. + * + * @param[in] map Pointer to the hash map to remove from + * and may not be NULL, so take caution. + * @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] callback Callback function which allows, + * for example, a dallocation of resources for the + * key and value pair to be removed. The function is + * invoked just before marking the key / value pair + * as removed. This may NOT be NULL, so take caution. + * @param[in] user_data Pointer to user specific data + * passed to the callback function in case more + * information is needed. + */ +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) +{ + const uint32_t hash = iscsi_hashmap_hash_data( key, key_size ); + iscsi_hashmap_bucket *entry = iscsi_hashmap_find_entry( map, key, key_size, hash ); + + if ( entry->key != NULL ) { + callback( entry->key, entry->key_size, entry->value, user_data ); + + entry->key = NULL; + entry->value = NULL; + + map->removed_count++; + } +} + +/** + * Returns the number of elements stored in the specified + * hash map. Elements marked for removal are not included. + * + * @param[in] map Pointer to the hash map to count the + * number of elements, may NOT be NULL, so take caution. + * @return Number of elements currently in use by the + * hash map. Buckets marked for removal are not counted. + */ +int iscsi_hashmap_size(iscsi_hashmap *map) +{ + return (map->count - map->removed_count); +} + +/** + * An iterator through the elements of a specified + * hash map which uses a callback function for each + * element not marked for removal, which also can + * abort the iteration, if necessary. + * + * @param[in] map Pointer to the hash map to iterate + * through, may NOT be NULL, so take caution. + * @param[in] callback Callback function to be + * invoked for each element not marked for removal + * in the hash map. If the return value of the callback + * function is below zero, the iteration will stop. + * @param[in] user_data Pointer to user specific data + * passed to the callback function in case more + * information is needed. + * @return The return code from the last invoked + * callback function. A negative value indicates an + * abortion of the iteration process. + */ +int iscsi_hashmap_iterate(iscsi_hashmap *map, iscsi_hashmap_callback callback, uint8_t *user_data) +{ + iscsi_hashmap_bucket *current = map->first; + int err = 0; + + while ( current != NULL ) { + if ( current->key != NULL ) { + err = callback( current->key, current->key_size, current->value, user_data ); + + if ( err < 0 ) + break; + } + + current = current->next; + } + + return err; +} + +/** + * Allocates an iSCSI packet data Basic Header Segment (BHS) + * and zero fills the structure. + * + * @return a pointer to BHS structure with all fields + * initialized or NULL if the allocation failed. + */ +iscsi_bhs_packet *iscsi_create_packet() +{ + iscsi_bhs_packet *bhs_pkt = (iscsi_bhs_packet *) malloc( sizeof(struct iscsi_bhs_packet) ); + + if ( bhs_pkt == NULL ) { + logadd( LOG_ERROR, "iscsi_create_packet: Out of memory while allocating BHS iSCSI packet data" ); + + return bhs_pkt; + } + + bhs_pkt->opcode = 0; // Initialize everything to zero + bhs_pkt->opcode_fields[0] = 0; + bhs_pkt->opcode_fields[1] = 0; + bhs_pkt->opcode_fields[2] = 0; + bhs_pkt->total_ahs_len = 0; + bhs_pkt->ds_len[0] = 0; + bhs_pkt->ds_len[1] = 0; + bhs_pkt->ds_len[2] = 0; + bhs_pkt->lun_opcode.lun = 0ULL; + bhs_pkt->init_task_tag = 0UL; + + memset( bhs_pkt->opcode_spec_fields, 0, sizeof(bhs_pkt->opcode_spec_fields) ); + + return bhs_pkt; +} + +/** + * Deallocates all aquired resources by iscsi_create_packet. + * + * @param[in] packet_data Pointer to packet data to deallocate. If this is + * NULL, this function does nothing. + */ +void iscsi_destroy_packet(iscsi_bhs_packet *packet_data) +{ + if (packet_data != NULL) + free( packet_data ); +} + +/** + * Constructs and appends an Additional Header Segment (AHS) 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! + * 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 a first + * AHS. + * @param[in] ahs_len Length of AHS packet data to be appended. + * @return New pointer to BHS structure with additional AHS attached or NULL in case + * of an reallocation error or total AHS length exceeds 255 DWORD's. + */ +iscsi_bhs_packet *iscsi_append_ahs_packet(iscsi_bhs_packet *packet_data, const uint32_t ahs_len) +{ + if ( packet_data == NULL ) { + packet_data = iscsi_create_packet(); + + if ( packet_data == NULL ) + return packet_data; + } + + 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 + 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" ); + + return NULL; + } + + packet_data = (iscsi_bhs_packet *) realloc( packet_data, new_pkt_size ); + + if ( packet_data == NULL ) { + logadd( LOG_ERROR, "iscsi_append_ahs_packet: Out of memory while allocating iSCSI AHS packet data for appending" ); + + return packet_data; + } + + iscsi_ahs_packet *ahs_pkt = (iscsi_ahs_packet *) ((uint8_t *) packet_data + old_pkt_size); + ahs_pkt->len = iscsi_get_be16(ahs_len); + ahs_pkt->type = 0; + ahs_pkt->specific = 0; + memset( ahs_pkt->data, 0, (new_pkt_size - old_pkt_size) - offsetof(struct iscsi_ahs_packet, data) ); + packet_data->total_ahs_len += (ahs_len + (ISCSI_ALIGN_SIZE - 1)) >> 2UL; + + return packet_data; +} + +/** + * Gets the total number of AHS packets. + * + * @param[in] packet_data Pointer to packet data of which the + * number of AHS packets should be counted. + * @return The number of AHS packets or zero in case none exist or + * -1 in case of error. + */ +int iscsi_get_ahs_packets(const iscsi_bhs_packet *packet_data) +{ + if ( packet_data == NULL ) + return -1; + else if ( packet_data->total_ahs_len == 0 ) + return 0; + + iscsi_ahs_packet *ahs_pkt = (iscsi_ahs_packet *) ((iscsi_bhs_packet *) packet_data + 1); // First AHS packet + int count = 0L; + uint32_t ahs_len = (uint32_t) packet_data->total_ahs_len << 2UL; + + while ( (int32_t) ahs_len > 0L ) { + uint32_t len = iscsi_get_be16(ahs_pkt->len) + offsetof(struct iscsi_ahs_packet, data); // Total length of current AHS packet + len = iscsi_align(len, ISCSI_ALIGN_SIZE); + + ahs_len -= len; + ahs_pkt = ((uint8_t *) ahs_pkt) + (len - offsetof(struct iscsi_ahs_packet, data)); // Advance pointer to next AHS packet + count++; + } + + return count; +} + +/** + * Gets the pointer of an AHS packet by specified index. + * + * @param[in] packet_data Pointer to packet data of which the + * AHS packet should be retrieved. + * @param[in] index Zero-based index number of AHS packet to + * be received. + * @return The pointer to the AHS packet at specified index on + * success or NULL in case of an error or if the specific index + * is out of range. + */ +iscsi_ahs_packet *iscsi_get_ahs_packet(const iscsi_bhs_packet *packet_data, const int index) +{ + if ( packet_data == NULL || (packet_data->total_ahs_len == 0) ) + return NULL; + + iscsi_ahs_packet *ahs_pkt = (iscsi_ahs_packet *) ((iscsi_bhs_packet *) packet_data + 1); // First AHS packet + int count = index; + uint32_t ahs_len = (uint32_t) packet_data->total_ahs_len << 2UL; + + while ( (int32_t) ahs_len > 0L ) { + if ( count-- < 0L ) + return ahs_pkt; + + uint32_t len = iscsi_get_be16(ahs_pkt->len) + offsetof(struct iscsi_ahs_packet, data); // Total length of current AHS packet + len = iscsi_align(len, ISCSI_ALIGN_SIZE); + + ahs_len -= len; + ahs_pkt = ((uint8_t *) ahs_pkt) + (len - offsetof(struct iscsi_ahs_packet, data)); // Advance pointer to next AHS packet + } + + logadd( LOG_ERROR, "iscsi_get_ahs_packet: Specified index for AHS packet does not exist" ); + + return NULL; +} + +/** + * Constructs and appends DataSegment (DS) 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! + * This function currently erases an already available DataSegment and + * throws away any data beyond DS. + * + * @param[in] packet_data Pointer to BHS packet data to append to. If NULL, a Basic + * Header Segment (BHS) will be created and initialized before adding the DataSegment. + * @param[in] header_digest_size Length of optional header digest (0 or 4 for now) to + * add. + * @param[in] ds_len Length of DataSegment packet data to be appended. May + * not exceed 16MiB - 1 (16777215 bytes). + * @param[in] data_digest_size Length of optional data digest (0 or 4 for now) to + * add. + * @return New pointer to BHS structure with additional DataSegment attached or + * NULL in case of an reallocation error, either header or data digest size does not + * confirm to the iSCSI standard or DS length exceeds 16777215 bytes. + */ +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) +{ + if ( ((header_digest_size != 0) && header_digest_size != ISCSI_DIGEST_SIZE) || ((data_digest_size != 0) && data_digest_size != ISCSI_DIGEST_SIZE) || (ds_len >= 16777216UL) ) + return NULL; + + if ( packet_data == NULL ) { + packet_data = iscsi_create_packet(); + + if ( packet_data == NULL ) + return packet_data; + } + + const uint32_t old_pkt_size = (const uint32_t) sizeof(struct iscsi_bhs_packet) + ((uint32_t) packet_data->total_ahs_len << 2UL); + const uint32_t new_pkt_size = old_pkt_size + header_digest_size + iscsi_align(ds_len, ISCSI_ALIGN_SIZE) + data_digest_size; + + packet_data = (iscsi_bhs_packet *) realloc( packet_data, new_pkt_size ); + + if ( packet_data == NULL ) { + logadd( LOG_ERROR, "iscsi_append_ds_packet: Out of memory while allocating iSCSI DS packet data for appending" ); + + return packet_data; + } + + 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; +} + +static const uint32_t crc32c_lut[] = { // Created with a polynomial reflect value of 0x82F63B78 + 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, + 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, + 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, + 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, + 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, + 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, + 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, + 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, + 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, + 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, + 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, + 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, + 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, + 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, + 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, + 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, + 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, + 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, + 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, + 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, + 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, + 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, + 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, + 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, + 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, + 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, + 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, + 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, + 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351}; + +/** + * Calculates CRC32C with 0x82F63B78 polynomial reflect according to iSCSI specs + * TODO: Implement optimized SSE4.2 and ARM versions + * + * @param[in] data Pointer to data to calculate CRC32C for. + * @param[in] len Length of data to be calculated. Must be + * divisable by 4 which is guaranteed by iSCSI standard. + * @param[in] crc32c Previous CRC32C in case of multiple passes. + * @return CRC32C value. THis function cannot fail. + */ +static inline uint32_t iscsi_crc32c_update(const uint8_t *data, const uint len, uint32_t crc32c) +{ + for ( uint i = 0; i < len; i += 4 ) { + crc32c = (crc32c >> 8UL) ^ crc32c_lut[(crc32c ^ data[i]) & 0xFF]; + crc32c = (crc32c >> 8UL) ^ crc32c_lut[(crc32c ^ data[i + 1]) & 0xFF]; + crc32c = (crc32c >> 8UL) ^ crc32c_lut[(crc32c ^ data[i + 2]) & 0xFF]; + crc32c = (crc32c >> 8UL) ^ crc32c_lut[(crc32c ^ data[i + 3]) & 0xFF]; + } + + return crc32c; +} + +/** + * Calculates header digest (CRC32C) with 0x82F63B78 polynomial reflect + * according to iSCSI specs and stores the result in the iSCSI packet + * data. This function cannot fail. + * + * @param[in] packet_data Pointer to ISCSI BHS packet to calculate CRC32C for. + */ +void iscsi_calc_header_digest(const iscsi_bhs_packet *packet_data) +{ + const uint32_t len = sizeof(struct iscsi_bhs_packet) + ((const uint32_t) packet_data->total_ahs_len << 2UL); + uint8_t *hdr_digest = ((uint8_t *) packet_data) + len; + const uint32_t crc32c = iscsi_crc32c_update( (const uint8_t *) packet_data, iscsi_align(len, 4), ISCSI_CRC32C_INITIAL ) ^ ISCSI_CRC32C_XOR; + + iscsi_put_be32( hdr_digest, crc32c ); +} + +/** + * Verifies header digest (CRC32C) with 0x82F63B78 polynomial reflect + * according to iSCSI specs. This function cannot fail. + * + * @param[in] packet_data Pointer to ISCSI BHS packet to validate CRC32C for. + * @return True if CRC32C matches the stored value, false otherwise. + */ +int iscsi_validate_header_digest(const iscsi_bhs_packet *packet_data) +{ + const uint32_t len = sizeof(struct iscsi_bhs_packet) + ((const uint32_t) packet_data->total_ahs_len << 2UL); + const uint8_t *hdr_digest = ((uint8_t *) packet_data) + len; + const uint32_t pkt_crc32c = *(uint32_t *) hdr_digest; + const uint32_t crc32c = iscsi_crc32c_update( (const uint8_t *) packet_data, len, ISCSI_CRC32C_INITIAL ) ^ ISCSI_CRC32C_XOR; + + return iscsi_get_be32(pkt_crc32c) == crc32c; +} + +/** + * Calculates data digest (CRC32) with 0x82F63B78 polynomial reflect + * of a whole DataSegment (CRC32C) according to the iSCSI specs. + * The resulting CRC32C will be stored in the iSCSI packet. + * + * @param[in] packet_data Pointer to ISCSI DS packet to calculate CRC32C for. + * @param[in] header_digest_size Length of optional header digest (0 or 4 for now) in + * order to calculate correct DataSegment index. The header digest size IS NOT checked + * for conforming to iSCSI specs, so be careful. + */ +void iscsi_calc_data_digest(const iscsi_bhs_packet *packet_data, const int header_digest_size) +{ + const uint32_t ds_idx = (const uint32_t) sizeof(struct iscsi_bhs_packet) + ((const uint32_t) packet_data->total_ahs_len << 2UL) + header_digest_size; + const uint8_t *data = ((uint8_t *) packet_data) + ds_idx; + const uint32_t ds_len = iscsi_get_be24(packet_data->ds_len); + const uint32_t len = iscsi_align(ds_len, 4); + uint8_t *data_digest = ((uint8_t *) packet_data) + ds_idx + len; + const uint32_t crc32c = iscsi_crc32c_update( data, len, ISCSI_CRC32C_INITIAL ) ^ ISCSI_CRC32C_XOR; + + iscsi_put_be32( data_digest, crc32c ); +} + +/** + * Verifies data digest (CRC32C) with 0x82F63B78 polynomial reflect + * according to iSCSI specs. This function cannot fail. + * + * @param[in] packet_data Pointer to ISCSI BHS packet to calculate CRC32C for. + * @param[in] header_digest_size Length of optional header digest (0 or 4 for now) in + * order to calculate correct DataSegment index. The header digest size IS NOT checked + * for conforming to iSCSI specs, so be careful. + * @return True if CRC32C matches the stored value, false otherwise. + */ +int iscsi_validate_data_digest(const iscsi_bhs_packet *packet_data, const int header_digest_size) +{ + const uint32_t ds_idx = (const uint32_t) sizeof(struct iscsi_bhs_packet) + ((const uint32_t) packet_data->total_ahs_len << 2UL) + header_digest_size; + const uint8_t *data = ((uint8_t *) packet_data) + ds_idx; + const uint32_t ds_len = iscsi_get_be24(packet_data->ds_len); + const uint32_t len = iscsi_align(ds_len, 4); + const uint8_t *data_digest = data + len; + const uint32_t pkt_crc32c = *(uint32_t *) data_digest; + const uint32_t crc32c = iscsi_crc32c_update( (const uint8_t *) data, len, ISCSI_CRC32C_INITIAL ) ^ ISCSI_CRC32C_XOR; + + return iscsi_get_be32(pkt_crc32c) == crc32c; +} + +/** + * Checks whether packet data is an iSCSI packet or not. + * Since iSCSI doesn't have a magic identifier for its packets, a + * partial heuristic approach is needed for it. There is not always + * a guarantee that a packet clearly belongs to iSCSI. If header + * and/or data digests are present, their respective CRC32C's are + * validated, too. Note that this function does NOT check if the + * iSCSI command fits in, e.g. a SCSI data in/out command without + * prior authentication, which is NOT allowed by the iSCSI standard, + * will NOT be considered an error by this function as it has no + * knowledge of prior commands sent. + * + * @param[in] packet_data Pointer to packet data to check for iSCSI, may + * not be NULL. + * @param[in] len Length of packet data to be checked for iSCSI, must be + * at least 48 bytes (minimum BHS header size). + * @param[in] header_digest_size Length of optional header digest (0 or 4 for now) in + * order to validate header digest. The header digest size IS NOT checked for + * conforming to iSCSI specs, so be careful. + * @param[in] data_digest_size Length of optional data digest (0 or 4 for now) in + * order to validate data digest size. The data digest size IS NOT checked for + * conforming to iSCSI specs, so take caution. + * @return 0 if it's clearly a iSCSI packet (detected without + * heuristics) or a positive integfer value up to 65536, if heuristics + * were necessary. A negative value is returned in case of an error or + * if it's clearly not an iSCSI packet. + */ +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) +{ + if ( packet_data == NULL ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_NO_DATA; + else if ( len < sizeof (struct iscsi_bhs_packet) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_SIZE_TOO_SMALL; + + const uint32_t ahs_len = (uint32_t) packet_data->total_ahs_len << 2UL; // AHS length is in DWORD's + const uint32_t ds_len = (uint32_t) iscsi_get_be24(packet_data->ds_len); + + if ( len != (sizeof (struct iscsi_bhs_packet) + ahs_len + header_digest_size + iscsi_align(ds_len, ISCSI_ALIGN_SIZE) + data_digest_size) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_SIZE_MISMATCH; + + const uint8_t opcode = ISCSI_GET_OPCODE(packet_data->opcode); + + switch ( opcode ) { + case ISCSI_CLIENT_NOP_OUT : { + if ( (int8_t) packet_data->opcode < 0 ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // MSB always MUST be cleared for this opcode -> invalid iSCSI packet data + + const iscsi_nop_out_packet *nop_out_pkt = (const iscsi_nop_out_packet *) packet_data; + + if ( (*(uint16_t *) nop_out_pkt->reserved != 0) || (nop_out_pkt->reserved[2] != 0) || (nop_out_pkt->reserved2[0] != 0) || (nop_out_pkt->reserved2[1] != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_CLIENT_SCSI_CMD : { + if ( (int8_t) packet_data->opcode < 0 ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // MSB always MUST be cleared for this opcode -> invalid iSCSI packet data + + const iscsi_scsi_cmd_packet *scsi_cmd_pkt = (const iscsi_scsi_cmd_packet *) packet_data; + + if ( scsi_cmd_pkt->reserved != 0 ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved field needs to be zero, but is NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_CLIENT_TASK_FUNC_REQ : { + if ( ((int8_t) packet_data->opcode < 0) || (ahs_len != 0) || (ds_len != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // MSB MUST always be cleared, AHS and DataSegment MUST be zero according to specs -> invalid iSCSI packet data + + const iscsi_task_mgmt_func_req_packet *task_mgmt_func_req_pkt = (const iscsi_task_mgmt_func_req_packet *) packet_data; + + if ( (task_mgmt_func_req_pkt->reserved != 0) || (task_mgmt_func_req_pkt->reserved2 != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_CLIENT_LOGIN_REQ : { + if ( (packet_data->opcode != (opcode | 0x40)) || (ahs_len != 0) || (ds_len != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Bit 6 always MUST be set, AHS and DataSegment MUST be zero according to specs -> invalid iSCSI packet data + + const iscsi_login_req_packet *login_req_pkt = (const iscsi_login_req_packet *) packet_data; + + 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->reserved != 0) || (login_req_pkt->reserved2[0] != 0) || (login_req_pkt->reserved2[1] != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_CLIENT_TEXT_REQ : { + if ( ((int8_t) packet_data->opcode < 0) || (ds_len == 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // MSB always MUST be cleared for this opcode and DataSegment is mandatory -> invalid iSCSI packet data + + const iscsi_text_req_packet *text_req_pkt = (const iscsi_text_req_packet *) packet_data; + + if ( (text_req_pkt->reserved != 0) || (text_req_pkt->reserved2[0] != 0) || (text_req_pkt->reserved2[1] != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_CLIENT_SCSI_DATA_OUT : { + if ( (packet_data->opcode != opcode) || (ds_len == 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Bits 6 and 7 always MUST be cleared for this opcode and DataSegment is mandatory -> invalid iSCSI packet data + + const iscsi_scsi_data_out_req_packet *scsi_data_out_req_pkt = (const iscsi_scsi_data_out_req_packet *) packet_data; + + if ( (scsi_data_out_req_pkt->reserved != 0) || (scsi_data_out_req_pkt->reserved2 != 0) || (scsi_data_out_req_pkt->reserved3 != 0) || (scsi_data_out_req_pkt->reserved4 != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_CLIENT_LOGOUT_REQ : { + if ( (int8_t) packet_data->opcode < 0 ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // MSB always MUST be cleared for this opcode -> invalid iSCSI packet data + + const iscsi_logout_req_packet *logout_req_pkt = (const iscsi_logout_req_packet *) packet_data; + + if ( (logout_req_pkt->reserved != 0) || (logout_req_pkt->reserved2 != 0) || (logout_req_pkt->reserved3 != 0) || (logout_req_pkt->reserved4[0] != 0) || (logout_req_pkt->reserved4[1] != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_CLIENT_SNACK_REQ : { + if ( packet_data->opcode != opcode ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Bits 6 and 7 always MUST be cleared for this opcode -> invalid iSCSI packet data + + const iscsi_snack_req_packet *snack_req_pkt = (const iscsi_snack_req_packet *) packet_data; + + if ( (snack_req_pkt->reserved != 0) || (snack_req_pkt->reserved2 != 0) || (snack_req_pkt->reserved3 != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_CLIENT_VENDOR_CODE1 : + case ISCSI_CLIENT_VENDOR_CODE2 : + case ISCSI_CLIENT_VENDOR_CODE3 : { + break; + } + case ISCSI_SERVER_NOP_IN : { + if ( packet_data->opcode != opcode ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Bits 6 and 7 always MUST be cleared for this opcode -> invalid iSCSI packet data + + const iscsi_nop_in_packet *nop_in_pkt = (const iscsi_nop_in_packet *) packet_data; + + if ( (*(uint16_t *) nop_in_pkt->reserved != 0) || (nop_in_pkt->reserved[2] != 0) || (nop_in_pkt->reserved2[0] != 0) || (nop_in_pkt->reserved2[1] != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_SERVER_SCSI_RESPONSE : { + if ( packet_data->opcode != opcode ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Bits 6 and 7 always MUST be cleared for this opcode -> invalid iSCSI packet data + + break; + } + case ISCSI_SERVER_TASK_FUNC_RES : { + if ( (packet_data->opcode != opcode) || (ahs_len != 0) || (ds_len != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Bits 6 and 7 always MUST be cleared, AHS and DataSegment MUST be zero according to specs -> invalid iSCSI packet data + + const iscsi_task_mgmt_func_response_packet *task_mgmt_func_response_pkt = (const iscsi_task_mgmt_func_response_packet *) packet_data; + + if ( (task_mgmt_func_response_pkt->reserved != 0) || (task_mgmt_func_response_pkt->reserved2 != 0) || (task_mgmt_func_response_pkt->reserved3 != 0) || (task_mgmt_func_response_pkt->reserved4 != 0) || (task_mgmt_func_response_pkt->reserved5 != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_SERVER_LOGIN_RES : { + if ( (packet_data->opcode != opcode) || (ds_len == 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Bits 6 and 7 always MUST be cleared for this opcode -> invalid iSCSI packet data and DataSegment is mandatory + + const iscsi_login_response_packet *login_response_pkt = (const iscsi_login_response_packet *) packet_data; + + 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->reserved != 0) || (login_response_pkt->reserved2 != 0) || (login_response_pkt->reserved3 != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_SERVER_TEXT_RES : { + if ( (packet_data->opcode != opcode) || (ds_len == 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Bits 6 and 7 always MUST be cleared for this opcode and DataSegment is mandatory -> invalid iSCSI packet data + + const iscsi_text_response_packet *text_response_pkt = (const iscsi_text_response_packet *) packet_data; + + if ( (text_response_pkt->reserved != 0) || (text_response_pkt->reserved2[0] != 0) || (text_response_pkt->reserved2[1] != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_SERVER_SCSI_DATA_IN : { + if ( (packet_data->opcode != opcode) || (ds_len == 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Bits 6 and 7 always MUST be cleared for this opcode and DataSegment is mandatory -> invalid iSCSI packet data + + const iscsi_scsi_data_in_response_packet *scsi_data_in_pkt = (const iscsi_scsi_data_in_response_packet *) packet_data; + + if ( scsi_data_in_pkt->reserved != 0 ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved field needs to be zero, but is NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_SERVER_LOGOUT_RES : { + if ( (packet_data->opcode != opcode) || (ahs_len != 0) || (ds_len != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Bits 6 and 7 always MUST be cleared, AHS and DataSegment MUST be zero according to specs -> invalid iSCSI packet data + + const iscsi_logout_response_packet *logout_response_pkt = (const iscsi_logout_response_packet *) packet_data; + + if ( (logout_response_pkt->reserved != 0) || (logout_response_pkt->reserved2 != 0) || (logout_response_pkt->reserved3 != 0) || (logout_response_pkt->reserved4 != 0) || (logout_response_pkt->reserved5 != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_SERVER_READY_XFER : { + if ( (packet_data->opcode != opcode) || (ahs_len != 0) || (ds_len != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // AHS and DataSegment MUST be zero according to specs -> invalid iSCSI packet data + + const iscsi_r2t_packet *r2t_pkt = (const iscsi_r2t_packet *) packet_data; + + if ( r2t_pkt->reserved != 0 ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved field needs to be zero, but is NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_SERVER_ASYNC_MSG : { + if ( (packet_data->opcode != opcode) || (ds_len == 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Bits 6 and 7 always MUST be cleared for this opcode and DataSegment is mandatory -> invalid iSCSI packet data + + const iscsi_async_msg_packet *async_msg_pkt = (const iscsi_async_msg_packet *) packet_data; + + if ( (async_msg_pkt->tag != 0xFFFFFFFFUL) || (async_msg_pkt->reserved != 0) || (async_msg_pkt->reserved2 != 0) || (async_msg_pkt->reserved3 != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT -> invalid iSCSI packet data + + break; + } + case ISCSI_SERVER_VENDOR_CODE1 : + case ISCSI_SERVER_VENDOR_CODE2 : + case ISCSI_SERVER_VENDOR_CODE3 : { + break; + } + case ISCSI_SERVER_REJECT : { + if ( packet_data->opcode != opcode ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Bits 6 and 7 always MUST be cleared according to specs -> invalid iSCSI packet data + + const iscsi_reject_packet *reject_pkt = (const iscsi_reject_packet *) packet_data; + + if ( (reject_pkt->tag != 0xFFFFFFFFUL) || (reject_pkt->reserved != 0) || (reject_pkt->reserved2 != 0) || (reject_pkt->reserved3 != 0) || (reject_pkt->reserved4[0] != 0) || (reject_pkt->reserved4[1] != 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS; // Reserved fields need all to be zero, but are NOT, tag always MUST be 0xFFFFFFFFUL -> invalid iSCSI packet data + + break; + } + default : { + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_INVALID_OPCODE; + + break; + } + } + + if ( (header_digest_size != 0) && (iscsi_validate_header_digest( packet_data ) == 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_CRC32C_HDR_DIGEST; + + if ( (data_digest_size != 0) && (iscsi_validate_data_digest( packet_data, header_digest_size ) == 0) ) + return ISCSI_VALIDATE_PACKET_RESULT_ERROR_CRC32C_DATA_DIGEST; + + return ISCSI_VALIDATE_PACKET_RESULT_OK; // All tests for iSCSI passed, return 0 +} + +/** + * Parses and extracts a specific key and value pair out of an iSCSI packet + * data stream amd puts the extracted data into a hash map to be used by + * the iSCSI implementation. + * + * @param[in] pairs Pointer to hash map containing all related keys and pairs. + * May NOT be NULL, so take caution. + * @param[in] packet_data Pointer to key / value pair to be parsed. NULL is + * an illegal value, so be careful. + * @param[in] len Length of the remaining packet data. + * @return Number of bytes used by the extracted key / vair pair or + * a negative value in case of an error. This can be used for + * incrementing the offset to the next key / value pair. + */ +static int iscsi_parse_text_key_value_pair(iscsi_hashmap *pairs, const uint8_t *packet_data, const uint32_t len) +{ + const uint key_val_len = strnlen( packet_data, len ); + const uint8_t *key_end = memchr( packet_data, '=', key_val_len ); + + if ( key_end == NULL ) { + logadd( LOG_ERROR, "iscsi_parse_text_key_value_pair: Key / value separator '=' not found" ); + + return -1L; + } + + const uint key_len = (key_end - packet_data); + + if ( key_len == 0 ) { + logadd( LOG_ERROR, "iscsi_parse_text_key_value_pair: Empty key found which is NOT allowed according to iSCSI specs" ); + + return -1L; + } + + if ( key_len > ISCSI_TEXT_KEY_MAX_LEN ) { + logadd( LOG_ERROR, "iscsi_parse_text_key_value_pair: Key value is too large (max 63 bytes)" ); + + return -1L; + } + + const uint hash_key_len = (key_len + 1UL); + uint8_t *hash_key = iscsi_hashmap_key_create( packet_data, hash_key_len ); + + if ( hash_key == NULL ) + return -1L; + + hash_key[key_len] = '\0'; + + if ( iscsi_hashmap_contains( pairs, hash_key, hash_key_len ) ) { + logadd( LOG_ERROR, "iscsi_parse_text_key_value_pair: Forbidden duplicate key discovered" ); + + iscsi_hashmap_key_destroy( hash_key ); + + return -1L; + } + + const uint val_len = strnlen( key_end + 1UL, key_val_len - key_len - 1UL ); + const uint max_len = (strcmp( hash_key, "CHAP_C" ) == 0) || (strcmp( hash_key, "CHAP_R" ) == 0) ? ISCSI_TEXT_VALUE_MAX_LEN : ISCSI_TEXT_VALUE_MAX_SIMPLE_LEN; + + if ( val_len > max_len ) { + logadd( LOG_ERROR, "iscsi_parse_text_key_value_pair: Value length larger than iSCSI specs allow" ); + + iscsi_hashmap_key_destroy( hash_key ); + + return -1L; + } + + const 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" ); + + iscsi_hashmap_key_destroy( hash_key ); + + return -1L; + } + + memcpy( hash_val, key_end + 1, val_len ); + + const int rc = iscsi_hashmap_put( pairs, hash_key, hash_key_len, hash_val ); + + if ( rc < 0 ) + return -1L; + + return hash_key_len + val_len + 1UL; // Number of bytes for processed key / value pair (+1 for '=' and NUL terminator) +} + +/** + * Parses and extracts all key and value pairs out of iSCSI packet + * data amd puts the extracted data into a hash map to be used by + * the iSCSI implementation. + * + * @param[in] pairs Pointer to hash map that should contain all + * extracted keys and pairs. May NOT be NULL, so take caution. + * @param[in] packet_data Pointer to first key and value pair to + * be parsed. NULL is an illegal value here, so be careful. + * @param[in] len Length of the remaining packet data. + * @param[in] cbit Non-zero value of C bit was set in previously. + * @param[in] partial_pairs Array of partial pair pointers in + * case C bit was set (multiple iSCSI packets for text data). + * @retval -1 An error occured during parsing key. + * @retval 0 Key and value pair was parsed successfully and was added to + * 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) +{ + if ( len == 0 ) + return 0L; // iSCSI specs don't allow zero length + + if ( (partial_pairs != NULL) && (*partial_pairs != NULL) ) { // Strip partial text parameters in case C bit was enabled previously + uint key_val_pair_len; + + 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 ); + + if ( tmp_partial_buf == NULL ) + return -1L; + + const int rc = iscsi_parse_text_key_value_pair( pairs, tmp_partial_buf, (key_val_pair_len + strlen( *partial_pairs )) ); + free( tmp_partial_buf ); + + if ( rc < 0 ) + return -1L; + + free( *partial_pairs ); + *partial_pairs = NULL; + + packet_data += (key_val_pair_len + 1); + len -= (key_val_pair_len + 1); + } + + if ( c_bit ) { // Strip partial parameters in case C bit was enabled previousley + if ( partial_pairs == NULL ) { + logadd( LOG_ERROR, "iscsi_parse_key_value_pairs: C bit set but missing partial parameter" ); + + return -1L; + } + + uint key_val_pair_len; + + for (key_val_pair_len = len - 1; (packet_data[key_val_pair_len] != '\0') && (key_val_pair_len > 0); key_val_pair_len--) { + } + + if ( key_val_pair_len != 0 ) + key_val_pair_len++; // NUL char found, don't copy to target buffer' + + *partial_pairs = (uint8_t *) malloc ( (len - key_val_pair_len) + 1 ); + + if ( *partial_pairs == NULL ) { + logadd( LOG_ERROR, "iscsi_parse_key_value_pairs: Out of memory allocating partial parameter" ); + + return -1L; + } + + memcpy( *partial_pairs, &packet_data[key_val_pair_len], (len - key_val_pair_len) ); + + if ( key_val_pair_len != 0 ) + len = key_val_pair_len - 1; + else + return 0L; + } + + int offset = 0L; + + while ( (offset < len) && (packet_data[offset] != '\0') ) { + const int rc = iscsi_parse_text_key_value_pair( pairs, (packet_data + offset), (len - offset) ); + + if ( rc < 0 ) + return -1L; + + offset += rc; + } + + return 0L; +} + +/** + * 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). + */ +iscsi_connection *iscsi_connection_create(const iscsi_login_req_packet *login_req_pkt) +{ + 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->key_value_pairs = iscsi_hashmap_create( 0UL ); + + 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->header_digest = 0L; + conn->data_digest = 0L; + + 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); + + 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 conn; +} + +/** + * 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->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 ); + } +} + +/** + * 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. + * @return Always returns 0a 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; +} diff --git a/src/server/iscsi.h b/src/server/iscsi.h new file mode 100644 index 0000000..f013a8f --- /dev/null +++ b/src/server/iscsi.h @@ -0,0 +1,2956 @@ +/* + * This file is part of the Distributed Network Block Device 3 + * + * Copyright(c) 2011-2012 Johann Latocha <johann@latocha.de> + * + * This file may be licensed under the terms of the + * GNU General Public License Version 2 (the ``GPL''). + * + * Software distributed under the License is distributed + * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See the GPL for the specific language + * governing rights and limitations. + * + * You should have received a copy of the GPL along with this + * program. If not, go to http://www.gnu.org/licenses/gpl.html + * or write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef DNBD3_ISCSI_H_ +#define DNBD3_ISCSI_H_ + +#include <inttypes.h> + +#if defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN) || (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#define iscsi_get_be16(x) (x) +#define iscsi_get_be24(x) ((x) & 0xFFFFFFUL) +#define iscsi_get_be32(x) (x) +#define iscsi_get_be64(x) (x) + +static inline void iscsi_put_be16(uint8_t *data, const uint16_t val) +{ + (*(uint16_t *) data) = val; +} + +static inline void iscsi_put_be24(uint8_t *data, const uint32_t val) +{ + (*(uint16_t *) data) = (uint16_t) (val >> 8U); + data[2] = (uint8_t) val; +} + +static inline void iscsi_put_be32(uint8_t *data, const uint32_t val) +{ + (*(uint32_t *) *data) = val; +} + +static inline void iscsi_put_be64(uint8_t *data, const uint64_t val) +{ + (*(uint64_t *) data) = val; +} +#elif defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(__i386__) || defined(__i386) || defined(__x86_64) +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) +// GCC or CLang +#define iscsi_get_be16(x) (__builtin_bswap16(x)) +#define iscsi_get_be24(x) (iscsi_get_be32(x) & 0xFFFFFFUL) +#define iscsi_get_be32(x) (__builtin_bswap32(x)) +#define iscsi_get_be64(x) (__builtin_bswap64(x)) +#elif defined(_MSC_VER) +#include <intrin.h> +// MVSC +#define iscsi_get_be16(x) (_byteswap_ushort(x)) +#define iscsi_get_be32(x) (_byteswap_ulong(x)) +#define iscsi_get_be64(x) (_byteswap_uint64(x)) +#else +// Other compilers (use slow conversion method with bit rotation, bit shift and logcal AND) +#define iscsi_get_be16(x) ((((uint16_t) (x)) << 8U) | (((uint16_t) (x)) >> 8U)) +#define iscsi_get_be32(x) ((((uint32_t) (x) & 0xFFUL) << 24UL) | (((uint32_t) (x) & 0xFF00UL) << 8UL) | (((uint32_t) (x) & 0xFF0000UL) >> 8UL) | (((uint32_t) (x) >> 24UL))) +#define iscsi_get_be64(x) ((uint64_t)((((x) & 0xFFULL) << 56ULL) | (((x) & 0xFF00ULL) << 40ULL) | (((x) & 0xFF0000ull) << 24ULL) | (((x) & 0xFF000000ULL) << 8ULL) | (((x) & 0xFF00000000ULL) >> 8ULL) | (((x) & 0xFF0000000000ULL) >> 24ULL) | (((x) & 0xFF000000000000ULL) >> 40ULL) | (((x) & 0xFF00000000000000ULL) >> 56ULL))) +#endif +static inline void iscsi_put_be16(uint8_t *data, const uint16_t val) +{ + data[0] = (uint8_t) (val >> 8U); + data[1] = (uint8_t) val; +} + +static inline void iscsi_put_be24(uint8_t *data, const uint32_t val) +{ + data[0] = (uint8_t) (val >> 16UL); + data[1] = (uint8_t) (val >> 8UL); + data[2] = (uint8_t) val; +} + +static inline void iscsi_put_be32(uint8_t *data, const uint32_t val) +{ + data[0] = (uint8_t) (val >> 24UL); + data[1] = (uint8_t) (val >> 16UL); + data[2] = (uint8_t) (val >> 8UL); + data[3] = (uint8_t) val; +} + +static inline void iscsi_put_be64(uint8_t *data, const uint64_t val) +{ + data[0] = (uint8_t) (val >> 56ULL); + data[1] = (uint8_t) (val >> 48ULL); + data[2] = (uint8_t) (val >> 40ULL); + data[3] = (uint8_t) (val >> 32ULL); + data[4] = (uint8_t) (val >> 24ULL); + data[5] = (uint8_t) (val >> 16ULL); + data[6] = (uint8_t) (val >> 8ULL); + data[7] = (uint8_t) val; +} +#else +#error "Unknown CPU endianness" +#endif + +// Align a value so that it's evenly divisable by n +#define iscsi_align(x, n) (((x) + (n) - 1) & ~((n) - 1)) + +uint8_t *iscsi_vsprintf_append_realloc(char *buf, const char *format, va_list args); // Allocates and appends a buffer and sprintf's it +uint8_t *iscsi_sprintf_append_realloc(char *buf, const char *format, ...); // Allocates and appends a buffer and sprintf's it +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 + +#define ISCSI_HASHMAP_DEFAULT_CAPACITY_SHIFT 5UL // Shift factor for default capacity +#define ISCSI_HASHMAP_DEFAULT_CAPACITY (1UL << (ISCSI_HASHMAP_DEFAULT_CAPACITY_SHIFT)) // Default capacity is 32 buckets +#define ISCSI_HASHMAP_RESIZE_SHIFT 1UL // Number of bits to shift left when resizing +#define ISCSI_HASHMAP_KEY_ALIGN_SHIFT 3UL // Key data shift value for alignment enforcement +#define ISCSI_HASHMAP_KEY_ALIGN (1UL << (ISCSI_HASHMAP_KEY_ALIGN_SHIFT)) // Key data size must be multiple of 8 bytes by now +#define ISCSI_HASHMAP_HASH_INITIAL 0x811C9DC5UL // Initial hash code +#define ISCSI_HASHMAP_HASH_MUL 0xBF58476D1CE4E5B9ULL // Value to multiply hash code with + +typedef struct iscsi_hashmap_bucket { + struct iscsi_hashmap_bucket *next; // Must be first element + + uint8_t *key; // Data used as key, zero padding + size_t key_size; // Size of key, must be a multiple of 8 bytes + uint32_t hash; // Hash code + uint8_t *value; // Value +} iscsi_hashmap_bucket; + +typedef struct iscsi_hashmap { + iscsi_hashmap_bucket *buckets; // Hashmap buckets + uint capacity; // Current capacity in elements + uint cap_load; // Capacity load threshold before next resize + uint count; // Number of buckets + uint removed_count; // Number of removed buckets + iscsi_hashmap_bucket *first; // First bucket of linked list + iscsi_hashmap_bucket *last; // Last bucket, allows faster traversion +} iscsi_hashmap; + +/** + * Callback function. This is a pointer to a + * function for various purposes like iterating + * through a hash map. It is also used for replacing + * already existing keys or for key removal. + * + * @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. + * @return A negative result indicates as fatal error, + * 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 + +iscsi_hashmap *hashmap_create(const uint capacity); // Creates an empty hash map with default capacity specified as above +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_val); // 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 + unless specified otherwise). */ + +#define ISCSI_BHS_SIZE 48UL // iSCSI Basic Header Segment size +#define ISCSI_DIGEST_SIZE 4UL // iSCSI header and data digest size (CRC32C) +#define ISCSI_ALIGN_SIZE 4UL // iSCSI packet data alignment (BHS, AHS and DS) + +#define ISCSI_VERSION_MIN 0 // Current minimum iSCSI protocol version supported by this implementation +#define ISCSI_VERSION_MAX 0 // Current maximum iSCSI protocol version supported by this implementation + +// CRC32C constants for header and data digest +#define ISCSI_CRC32C_INITIAL 0xFFFFFFFFUL +#define ISCSI_CRC32C_XOR 0xFFFFFFFFUL + +// iSCSI initiator (client) command opcodes +#define ISCSI_CLIENT_NOP_OUT 0x00 // NOP-Out +#define ISCSI_CLIENT_SCSI_CMD 0x01 // SCSI Command (encapsulates a SCSI Command Descriptor Block) +#define ISCSI_CLIENT_TASK_FUNC_REQ 0x02 // SCSI Task Management Function Request +#define ISCSI_CLIENT_LOGIN_REQ 0x03 // Login Request +#define ISCSI_CLIENT_TEXT_REQ 0x04 // Text Request +#define ISCSI_CLIENT_SCSI_DATA_OUT 0x05 // SCSI Data-Out (for write operations) +#define ISCSI_CLIENT_LOGOUT_REQ 0x06 // Logout Request +#define ISCSI_CLIENT_SNACK_REQ 0x10 // SNACK Request +#define ISCSI_CLIENT_VENDOR_CODE1 0x1C // Vendor-specific code #1 +#define ISCSI_CLIENT_VENDOR_CODE2 0x1D // Vendor-specific code #2 +#define ISCSI_CLIENT_VENDOR_CODE3 0x1E // Vendor-specific code #3 + +#define ISCSI_CLIENT_FIRST_OPCODE 0x00 // First client opcode value +#define ISCSI_CLIENT_LAST_OPCODE 0x1F // Last client opcode value + +// iSCSI target (server) command opcodes +#define ISCSI_SERVER_NOP_IN 0x20 // NOP-In +#define ISCSI_SERVER_SCSI_RESPONSE 0x21 // SCSI Response - contains SCSI status and possibly sense information or other response information +#define ISCSI_SERVER_TASK_FUNC_RES 0x22 // SCSI Task Management Function Response +#define ISCSI_SERVER_LOGIN_RES 0x23 // Login Response +#define ISCSI_SERVER_TEXT_RES 0x24 // Text Response +#define ISCSI_SERVER_SCSI_DATA_IN 0x25 // SCSI Data-In (for read operations) +#define ISCSI_SERVER_LOGOUT_RES 0x26 // Logout Response +#define ISCSI_SERVER_READY_XFER 0x31 // Ready To Transfer (R2T) - sent by target when it is ready to receive data +#define ISCSI_SERVER_ASYNC_MSG 0x32 // Asynchronous Message - sent by target to indicate certain special conditions +#define ISCSI_SERVER_VENDOR_CODE1 0x3C // Vendor-specific code #1 +#define ISCSI_SERVER_VENDOR_CODE2 0x3D // Vendor-specific code #2 +#define ISCSI_SERVER_VENDOR_CODE3 0x3E // Vendor-specific code #3 +#define ISCSI_SERVER_REJECT 0x3F // Reject + +#define ISCSI_SERVER_FIRST_OPCODE 0x20 // First client opcode value +#define ISCSI_SERVER_LAST_OPCODE 0x3F // Last client opcode value + +#define ISCSI_OPCODE_MASK 0x3F // ISCSI opcode bit mask (bits 0-5 used) +#define ISCSI_GET_OPCODE(x) ((x) & ISCSI_OPCODE_MASK) // Funky macro to get iSCSI packet opcode + +typedef struct __attribute__((packed)) iscsi_bhs_packet { + uint8_t opcode; // Command opcode (see above) + uint8_t opcode_fields[3]; // Opcode-specific fields + uint8_t total_ahs_len; // Total AHS length + uint8_t ds_len[3]; // Data segment length + union { + uint64_t lun; // LUN bitmask + uint8_t opcode_spec[8]; // Opcode-specific fields + } lun_opcode; + uint32_t init_task_tag; // Initiator task tag + uint8_t opcode_spec_fields[28]; +} iscsi_bhs_packet; + +#define ISCSI_AHS_TYPE_EXT_CDB_PACKET 0x01 // Command Descriptor Block (CDB) +#define ISCSI_AHS_TYPE_BIDI_READ_EXP_XFER_AHS_PACKET 0x02 + +typedef struct __attribute__((packed)) iscsi_ahs_packet { + uint16_t len; // AHSLength + uint8_t type; // AHSType + uint8_t specific; // AHS-Specific + uint8_t data[0]; // AHS-Specific data +} iscsi_ahs_packet; + +/* There are 16 bytes in the CDB field to accommodate the commonly used + CDBs. Whenever the CDB is larger than 16 bytes, an Extended CDB AHS + MUST be used to contain the CDB spillover. +*/ +typedef struct __attribute__((packed)) iscsi_cdb { + uint8_t data[16]; +} iscsi_cdb; + +/* This type of AHS MUST NOT be used if the CDBLength is less than 17. + The length includes the reserved byte 3. +*/ +typedef struct __attribute__((packed)) iscsi_ext_cdb_ahs_packet { + uint16_t len; // AHSLength - (CDBLength - 15) + uint8_t type; // Identifier (always 0x01 according to specs) + uint8_t reserved; // Reserved for future usage + uint8_t data[0]; // ExtendedCDB +} iscsi_ext_cdb_ahs_packet; + +typedef struct __attribute__((packed)) iscsi_bidi_read_exp_xfer_ahs_packet { + uint16_t len; // Always 0x0005 according to specs + uint8_t type; // Identifier (always 0x02 according to specs) + uint8_t reserved; // Reserved for future usage + uint32_t bidi_read_exp_xfer_len; // Bidirectional Read Expected Data Transfer Length +} iscsi_bidi_read_exp_xfer_ahs_packet; + +/* Certain iSCSI conditions result in the command being terminated at + the target (response code of Command Completed at Target) with a SCSI + CHECK CONDITION Status as outlined in the following definitions + (Sense key: Aborted Command 0x0B): +*/ +#define ISCSI_DS_ERROR_UNEXPECTED_UNSOLICITED_DATA_ASC 0x0C // Unexpected unsolicited data +#define ISCSI_DS_ERROR_UNEXPECTED_UNSOLICITED_DATA_ASCQ 0x0C + +#define ISCSI_DS_ERROR_INCORRECT_AMOUNT_OF_DATA_ASC 0x0C // Incorrect amount of data +#define ISCSI_DS_ERROR_INCORRECT_AMOUNT_OF_DATA_ASCQ 0x0D + +#define ISCSI_DS_ERROR_PROTOCOL_SERVICE_CRC_ERROR_ASC 0x47 // Protocol Service CRC error +#define ISCSI_DS_ERROR_PROTOCOL_SERVICE_CRC_ERROR_ASCQ 0x05 + +#define ISCSI_DS_ERROR_SNACK_REJECTED_ASC 0x11 // SNACK rejected +#define ISCSI_DS_ERROR_SNACK_REJECTED_ASCQ 0x13 + +/* Optional header and data digests protect the integrity of the header + and data, respectively. The digests, if present, are located, + respectively, after the header and PDU-specific data and cover, + respectively, the header and the PDU data, each including the padding + bytes, if any. + + The existence and type of digests are negotiated during the Login + Phase. +*/ +typedef struct __attribute__((packed)) iscsi_header_digest { + uint32_t crc32c; // Header digest is a CRC32C for ensuring integrity +} iscsi_header_digest; + +typedef struct __attribute__((packed)) iscsi_data_digest { + uint32_t crc32c; // Data digest is a CRC32C for ensuring integrity +} iscsi_data_digest; + +/* iSCSI targets MUST support and enable Autosense. If Status is CHECK + CONDITION (0x02), then the data segment MUST contain sense data for + the failed command. + + For some iSCSI responses, the response data segment MAY contain some + response-related information (e.g., for a target failure, it may + contain a vendor-specific detailed description of the failure). +*/ + +typedef struct __attribute__((packed)) iscsi_ds_cmd_data { + uint16_t len; // SenseLength - This field indicates the length of Sense Data. + uint8_t sense_data[0]; // The Sense Data contains detailed information about a CHECK CONDITION. + // SPC3 specifies the format and content of the Sense Data. + uint8_t res_data[0]; // Response Data +} iscsi_ds_cmd_data; + +// SCSI command opcodes (embedded in iSCSI protocol) +#define SCSI_OPCODE_TESTUNITREADY 0x00 // TEST UNIT READY +#define SCSI_OPCODE_READ6 0x08 // READ(6) +#define SCSI_OPCODE_INQUIRY 0x12 // INQUIRY +#define SCSI_OPCODE_MODESELECT6 0x15 // MODE SELECT(6) +#define SCSI_OPCODE_RESERVE6 0x16 // RESERVE(6) +#define SCSI_OPCODE_RELEASE6 0x17 // RELEASE(6) +#define SCSI_OPCODE_MODESENSE6 0x1A // MODE SENSE(6) +#define SCSI_OPCODE_STARTSTOPUNIT 0x1B // START STOP UNIT +#define SCSI_OPCODE_PREVENTALLOW 0x1E // PREVENT ALLOW MEDIUM REMOVAL +#define SCSI_OPCODE_READCAPACITY10 0x25 // READ CAPACITY(10) +#define SCSI_OPCODE_READ10 0x28 // READ(10) +#define SCSI_OPCODE_WRITE10 0x2A // WRITE(10) +#define SCSI_OPCODE_WRITE_VERIFY10 0x2E // WRITE AND VERIFY(10) +#define SCSI_OPCODE_VERIFY10 0x2F // VERIFY(10) +#define SCSI_OPCODE_PREFETCH10 0x34 // PRE-FETCH(10) +#define SCSI_OPCODE_SYNCHRONIZECACHE10 0x35 // SYNCHRONIZE CACHE(10) +#define SCSI_OPCODE_READ_DEFECT_DATA10 0x37 // READ DEFECT DATA(10) +#define SCSI_OPCODE_WRITE_SAME10 0x41 // WRITE SAME(10) +#define SCSI_OPCODE_UNMAP 0x42 // UNMAP +#define SCSI_OPCODE_READTOC 0x43 // READ TOC/PMA/ATIP +#define SCSI_OPCODE_SANITIZE 0x48 // SANITIZE +#define SCSI_OPCODE_MODESELECT10 0x55 // MODE SELECT(10) +#define SCSI_OPCODE_MODESENSE10 0x5A // MODE SENSE(10) +#define SCSI_OPCODE_PERSISTENT_RESERVE_IN 0x5E // PERSISTENT RESERVE IN +#define SCSI_OPCODE_PERSISTENT_RESERVE_OUT 0x5F // PERSISTENT RESERVE OUT +#define SCSI_OPCODE_EXTENDED_COPY 0x83 // Third-party Copy OUT +#define SCSI_OPCODE_RECEIVE_COPY_RESULTS 0x84 // Third-party Copy IN +#define SCSI_OPCODE_READ16 0x88 // READ(16) +#define SCSI_OPCODE_COMPARE_AND_WRITE 0x89 // COMPARE AND WRITE +#define SCSI_OPCODE_WRITE16 0x8A // WRITE(16) +#define SCSI_OPCODE_ORWRITE 0x8B // ORWRITE +#define SCSI_OPCODE_WRITE_VERIFY16 0x8E // WRITE AND VERIFY(16) +#define SCSI_OPCODE_VERIFY16 0x8F // VERIFY(16) +#define SCSI_OPCODE_PREFETCH16 0x90 // PRE-FETCH(16) +#define SCSI_OPCODE_SYNCHRONIZECACHE16 0x91 // SYNCHRONIZE CACHE(16) +#define SCSI_OPCODE_WRITE_SAME16 0x93 // WRITE SAME(16) +#define SCSI_OPCODE_WRITE_ATOMIC16 0x9C // WRITE ATOMIC(16) +#define SCSI_OPCODE_SERVICE_ACTION_IN 0x9E // SERVICE ACTION IN(16) +#define SCSI_OPCODE_REPORTLUNS 0xA0 // REPORT LUNS +#define SCSI_OPCODE_MAINTENANCE_IN 0xA3 // MAINTENANCE IN +#define SCSI_OPCODE_READ12 0xA8 // READ(12) +#define SCSI_OPCODE_WRITE12 0xAA // WRITE(12) +#define SCSI_OPCODE_WRITE_VERIFY12 0xAE // WRITE AND VERIFY(12) +#define SCSI_OPCODE_VERIFY12 0xAF // VERIFY(12) +#define SCSI_OPCODE_READ_DEFECT_DATA12 0xB7 // READ DEFECT DATA(12) + +#define ISCSI_SCSI_CMD_FLAGS_TASK_NO_UNSOLICITED_DATA (1 << 7) // (F) is set to 1 when no unsolicited SCSI Data-Out PDUs + // follow this PDU. When F = 1 for a write and if Expected + // Data Transfer Length is larger than the + // DataSegmentLength, the target may solicit additional data + // through R2T. +#define ISCSI_SCSI_CMD_FLAGS_TASK_READ (1 << 6) // (R) is set to 1 when the command is expected to input data +#define ISCSI_SCSI_CMD_FLAGS_TASK_WRITE (1 << 5) // (W) is set to 1 when the command is expected to output data + +#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_UNTAGGED 0x0 // Untagged task attribute +#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_SIMPLE 0x1 // Simple task attribute +#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_ORDERED 0x2 // Ordered task attribute +#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_HEAD_QUEUE 0x3 // Head of queue task attribute +#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_ACA 0x4 // ACA task attribute +#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_RESERVED_1 0x5 // ACA task attribute +#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_RESERVED_2 0x6 // ACA task attribute +#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_RESERVED_3 0x7 // ACA task attribute + +/* Flags and Task Attributes: + At least one of the W and F bits MUST be set to 1. + Either or both of R and W MAY be 1 when the Expected Data Transfer + Length and/or the Bidirectional Read Expected Data Transfer Length + are 0, but they MUST NOT both be 0 when the Expected Data Transfer + Length and/or Bidirectional Read Expected Data Transfer Length are + not 0 (i.e., when some data transfer is expected, the transfer + direction is indicated by the R and/or W bit). +*/ + +#define ISCSI_SCSI_CMD_FLAGS_TASK_ATTR_MASK 0x7 // Task Attributes (ATTR) are encoded in the first three LSBs' + +typedef struct __attribute__((packed)) iscsi_scsi_cmd_packet { + uint8_t opcode; // Always 0x01 according to specification (see above) + int8_t flags_task; // Flags and Task Attributes + uint16_t reserved; // Reserved for future usage + uint8_t total_ahs_len; // Total AHS length + uint8_t ds_len[3]; // Data segment length + uint64_t lun; // LUN bitmask + uint32_t init_task_tag; // Initiator task tag + uint32_t exp_xfer_len; // Expected Data Transfer Length + // For unidirectional operations, the Expected Data Transfer Length + // field contains the number of bytes of data involved in this SCSI + // operation. For a unidirectional write operation (W flag set to 1 and + // R flag set to 0), the initiator uses this field to specify the number + // of bytes of data it expects to transfer for this operation. For a + // unidirectional read operation (W flag set to 0 and R flag set to 1), + // the initiator uses this field to specify the number of bytes of data + // it expects the target to transfer to the initiator. It corresponds + // to the SAM-2 byte count. + // For bidirectional operations (both R and W flags are set to 1), this + // field contains the number of data bytes involved in the write + // transfer. For bidirectional operations, an additional header segment + // MUST be present in the header sequence that indicates the + // Bidirectional Read Expected Data Transfer Length. The Expected Data + // Transfer Length field and the Bidirectional Read Expected Data + // Transfer Length field correspond to the SAM-2 byte count. + // If the Expected Data Transfer Length for a write and the length of + // the immediate data part that follows the command (if any) are the + // same, then no more data PDUs are expected to follow. In this case, + // the F bit MUST be set to 1. + // If the Expected Data Transfer Length is higher than the + // FirstBurstLength (the negotiated maximum amount of unsolicited data + // the target will accept), the initiator MUST send the maximum amount + // of unsolicited data OR ONLY the immediate data, if any. + // Upon completion of a data transfer, the target informs the initiator + // (through residual counts) of how many bytes were actually processed + // (sent and/or received) by the target. + uint32_t cmd_sn; // The CmdSN enables ordered delivery across multiple connections in a single session + uint32_t exp_stat_sn; // Command responses up to ExpStatSN - 1 (modulo 2**32) have been + // received (acknowledges status) on the connection. + struct iscsi_cdb scsi_cdb; // SCSI Command Descriptor Block (CDB) + // There are 16 bytes in the CDB field to accommodate the commonly used + // CDBs. Whenever the CDB is larger than 16 bytes, an Extended CDB AHS + // MUST be used to contain the CDB spillover. + struct iscsi_ahs_packet ahs; // Optional AHS packet data + struct iscsi_header_digest hdr_digest; // Optional header digest + struct iscsi_ds_cmd_data ds_cmd_data; // Optional data segment, command data + struct iscsi_data_digest data_digest; // Optional data digest +} iscsi_scsi_cmd_packet; + +#define ISCSI_SCSI_RESPONSE_FLAGS_RES_UNDERFLOW (1 << 1) // (U) set for Residual Underflow. In this case, the Residual + // Count indicates the number of bytes that were not + // transferred out of the number of bytes that were expected + // to be transferred. For a bidirectional operation, the + // Residual Count contains the residual for the write + // operation. + +#define ISCSI_SCSI_RESPONSE_FLAGS_RES_OVERFLOW (1 << 2) // (O) set for Residual Overflow. In this case, the Residual + // Count indicates the number of bytes that were not + // transferred because the initiator's Expected Data + // Transfer Length was not sufficient. For a bidirectional + // operation, the Residual Count contains the residual for + // the write operation. + +#define ISCSI_SCSI_RESPONSE_FLAGS_BIDI_READ_RES_UNDERFLOW (1 << 3) // (u) set for Bidirectional Read Residual Underflow. In this + // case, the Bidirectional Read Residual Count indicates the + // number of bytes that were not transferred to the + // initiator out of the number of bytes expected to be + // transferred. + +#define ISCSI_SCSI_RESPONSE_FLAGS_BIDI_READ_RES_OVERFLOW (1 << 4) // (o) set for Bidirectional Read Residual Overflow. In this + // case, the Bidirectional Read Residual Count indicates the + // number of bytes that were not transferred to the + // initiator because the initiator's Bidirectional Read + // Expected Data Transfer Length was not sufficient. + +/* Bits O and U and bits o and u are mutually exclusive (i.e., having + both o and u or O and U set to 1 is a protocol error). + + For a response other than "Command Completed at Target", bits 3-6 + MUST be 0. +*/ + +#define ISCSI_SCSI_RESPONSE_STATUS_GOOD 0x00 +#define ISCSI_SCSI_RESPONSE_STATUS_CHECK_COND 0x02 +#define ISCSI_SCSI_RESPONSE_STATUS_BUSY 0x08 +#define ISCSI_SCSI_RESPONSE_STATUS_RES_CONFLICT 0x18 +#define ISCSI_SCSI_RESPONSE_STATUS_TASK_SET_FULL 0x28 +#define ISCSI_SCSI_RESPONSE_STATUS_ACA_ACTIVE 0x30 +#define ISCSI_SCSI_RESPONSE_STATUS_TASK_ABORTED 0x40 + +/* The Status field is used to report the SCSI status of the command (as + specified in SAM2) and is only valid if the response code is + Command Completed at Target. + + If a SCSI device error is detected while data from the initiator is + still expected (the command PDU did not contain all the data and the + target has not received a data PDU with the Final bit set), the + target MUST wait until it receives a data PDU with the F bit set in + the last expected sequence before sending the Response PDU. +*/ + +#define ISCSI_SCSI_RESPONSE_CODE_OK 0x00 // Command Completed at Target +#define ISCSI_SCSI_RESPONSE_CODE_FAIL 0x01 // Target Failure +#define ISCSI_SCSI_RESPONSE_CODE_VENDOR_FIRST 0x80 // First vendor specific response code +#define ISCSI_SCSI_RESPONSE_CODE_VENDOR_LAST 0xFF // Last vendor specific response code + +/* The Response field is used to report a service response. The mapping + of the response code into a SCSI service response code value, if + needed, is outside the scope of this document. However, in symbolic + terms, response value 0x00 maps to the SCSI service response (see +*/ +typedef struct __attribute__((packed)) iscsi_scsi_response_packet { + uint8_t opcode; // Always 0x21 according to specification (see above) + int8_t flags; // Flags (see above) + uint8_t response; // This field contains the iSCSI service response. + uint8_t status; // The Status field is used to report the SCSI status of the command (as + // specified in SAM2) and is only valid if the response code is + // Command Completed at Target. See above for codes. + uint8_t total_ahs_len; // Total AHS length + uint8_t ds_len[3]; // Data segment length + uint64_t reserved; // Reserved for future usage + uint32_t init_task_tag; // Initiator task tag + uint32_t snack_tag; // This field contains a copy of the SNACK Tag of the last SNACK Tag + // accepted by the target on the same connection and for the command for + // which the response is issued. Otherwise, it is reserved and should + // be set to 0. + // After issuing a R-Data SNACK, the initiator must discard any SCSI + // status unless contained in a SCSI Response PDU carrying the same + // SNACK Tag as the last issued R-Data SNACK for the SCSI command on the + // current connection. + uint32_t stat_sn; // StatSN - Status Sequence Number + // The StatSN is a sequence number that the target iSCSI layer generates + // per connection and that in turn enables the initiator to acknowledge + // status reception. The StatSN is incremented by 1 for every + // response/status sent on a connection, except for responses sent as a + // result of a retry or SNACK. In the case of responses sent due to a + // retransmission request, the StatSN MUST be the same as the first time + // the PDU was sent, unless the connection has since been restarted. + uint32_t exp_cmd_sn; // ExpCmdSN - Next Expected CmdSN from This Initiator + // The ExpCmdSN is a sequence number that the target iSCSI returns to + // the initiator to acknowledge command reception. It is used to update + // a local variable with the same name. An ExpCmdSN equal to + // MaxCmdSN + 1 indicates that the target cannot accept new commands. + uint32_t max_cmd_sn; // MaxCmdSN - Maximum CmdSN from This Initiator + // The MaxCmdSN is a sequence number that the target iSCSI returns to + // the initiator to indicate the maximum CmdSN the initiator can send. + // It is used to update a local variable with the same name. If the + // MaxCmdSN is equal to ExpCmdSN - 1, this indicates to the initiator + // that the target cannot receive any additional commands. When the + // MaxCmdSN changes at the target while the target has no pending PDUs + // to convey this information to the initiator, it MUST generate a + // NOP-In to carry the new MaxCmdSN. + uint32_t exp_data_sn; // ExpDataSN or Reserved + // This field indicates the number of Data-In (read) PDUs the target has + // sent for the command. + // This field MUST be 0 if the response code is not Command Completed at + // Target or the target sent no Data-In PDUs for the command. + uint32_t bidi_read_res_cnt; // Bidirectional Read Residual Count or Reserved + // The Bidirectional Read Residual Count field MUST be valid in the case + // where either the u bit or the o bit is set. If neither bit is set, + // the Bidirectional Read Residual Count field is reserved. Targets may + // set the Bidirectional Read Residual Count, and initiators may use it + // when the response code is Command Completed at Target. If the o bit + // is set, the Bidirectional Read Residual Count indicates the number of + // bytes that were not transferred to the initiator because the + // initiator's Bidirectional Read Expected Data Transfer Length was not + // sufficient. If the u bit is set, the Bidirectional Read Residual + // Count indicates the number of bytes that were not transferred to the + // initiator out of the number of bytes expected to be transferred. + uint32_t res_cnt; // Residual Count or Reserved + // The Residual Count field MUST be valid in the case where either the U + // bit or the O bit is set. If neither bit is set, the Residual Count + // field MUST be ignored on reception and SHOULD be set to 0 when + // sending. Targets may set the residual count, and initiators may use + // it when the response code is Command Completed at Target (even if the + // status returned is not GOOD). If the O bit is set, the Residual + // Count indicates the number of bytes that were not transferred because + // the initiator's Expected Data Transfer Length was not sufficient. If + // the U bit is set, the Residual Count indicates the number of bytes + // that were not transferred out of the number of bytes expected to be + // transferred. + struct iscsi_header_digest hdr_digest; // Optional header digest + struct iscsi_ds_cmd_data ds_cmd_data; // Optional data segment, command data + struct iscsi_data_digest data_digest; // Optional data digest +} iscsi_scsi_response_packet; + +#define ISCSI_TASK_MGMT_FUNC_REQ_FUNC_ABORT_TASK 0x01 // ABORT TASK - aborts the task identified by the Referenced Task Tag field +#define ISCSI_TASK_MGMT_FUNC_REQ_FUNC_ABORT_TASK_SET 0x02 // ABORT TASK SET - aborts all tasks issued via this session on the LU +#define ISCSI_TASK_MGMT_FUNC_REQ_FUNC_CLEAR_ACA 0x03 // CLEAR ACA - clears the Auto Contingent Allegiance condition +#define ISCSI_TASK_MGMT_FUNC_REQ_FUNC_CLEAR_TASK_SET 0x04 // CLEAR TASK SET - aborts all tasks in the appropriate task set + // as defined by the TST field in the Control mode page + // (see SPC3) +#define ISCSI_TASK_MGMT_FUNC_REQ_FUNC_LOGICAL_UNIT_RESET 0x05 // LOGICAL UNIT RESET +#define ISCSI_TASK_MGMT_FUNC_REQ_FUNC_TARGET_WARM_RESET 0x06 // TARGET WARM RESET +#define ISCSI_TASK_MGMT_FUNC_REQ_FUNC_TARGET_COLD_RESET 0x07 // TARGET COLD RESET +#define ISCSI_TASK_MGMT_FUNC_REQ_FUNC_TASK_REASSIGN 0x08 // TASK REASSIGN - reassigns connection allegiance for the task + // identified by the Initiator Task Tag field to this connection, + // thus resuming the iSCSI exchanges for the task + +typedef struct __attribute__((packed)) iscsi_task_mgmt_func_req_packet { + uint8_t opcode; // Always 0x02 according to specification (see above) + uint8_t func; // Function. + // The task management functions provide an initiator with a way to + // explicitly control the execution of one or more tasks (SCSI and iSCSI + // tasks). The task management function codes are listed below. For a + // more detailed description of SCSI task management, see SAM2. + uint16_t reserved; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength (MUST be 0 for this PDU) + uint8_t ds_len[3]; // DataSegmentLength (MUST be 0 for this PDU) + uint64_t lun; // Logical Unit Number (LUN) or Reserved + // This field is required for functions that address a specific LU + // (ABORT TASK, CLEAR TASK SET, ABORT TASK SET, CLEAR ACA, LOGICAL UNIT + // RESET) and is reserved in all others + uint32_t init_task_tag; // Initiator task tag + // This is the Initiator Task Tag of the task to be aborted for the + // ABORT TASK function or reassigned for the TASK REASSIGN function. + // For all the other functions, this field MUST be set to the reserved + // value 0xFFFFFFFF + uint32_t ref_task_tag; // Referenced task tag or 0xFFFFFFFF + uint32_t cmd_sn; // CmdSN + uint32_t exp_stat_sn; // ExpStatSN + uint32_t ref_cmd_sn; // RefCmdSN or Reserved + // If an ABORT TASK is issued for a task created by an immediate + // command, then the RefCmdSN MUST be that of the task management + // request itself (i.e., the CmdSN and RefCmdSN are equal). + // For an ABORT TASK of a task created by a non-immediate command, the + // RefCmdSN MUST be set to the CmdSN of the task identified by the + // Referenced Task Tag field. Targets must use this field when the task + // identified by the Referenced Task Tag field is not with the target. + // Otherwise, this field is reserved + uint32_t exp_data_sn; // ExpDataSN or Reserved + // For recovery purposes, the iSCSI target and initiator maintain a data + // acknowledgment reference number - the first input DataSN number + // unacknowledged by the initiator. When issuing a new command, this + // number is set to 0. If the function is TASK REASSIGN, which + // establishes a new connection allegiance for a previously issued read + // or bidirectional command, the ExpDataSN will contain an updated data + // acknowledgment reference number or the value 0; the latter indicates + // that the data acknowledgment reference number is unchanged. The + // initiator MUST discard any data PDUs from the previous execution that + // it did not acknowledge, and the target MUST transmit all Data-In PDUs + // (if any) starting with the data acknowledgment reference number. The + // number of retransmitted PDUs may or may not be the same as the + // original transmission, depending on if there was a change in + // MaxRecvDataSegmentLength in the reassignment. The target MAY also + // send no more Data-In PDUs if all data has been acknowledged. + // The value of ExpDataSN MUST be 0 or higher than the DataSN of the + // last acknowledged Data-In PDU, but not larger than DataSN + 1 of the + // last Data-IN PDU sent by the target. Any other value MUST be ignored + // by the target. + // For other functions, this field is reserved + uint64_t reserved2; // Reserved for future usage + struct iscsi_header_digest hdr_digest; // Optional header digest +} iscsi_task_mgmt_func_req_packet; + +#define ISCSI_TASK_MGMT_FUNC_RESPONSE_FUNC_COMPLETE 0x00 // Function complete +#define ISCSI_TASK_MGMT_FUNC_RESPONSE_TASK_NO_EXIST 0x01 // Task does not exist +#define ISCSI_TASK_MGMT_FUNC_RESPONSE_LUN_NO_EXIST 0x02 // LUN does not exist +#define ISCSI_TASK_MGMT_FUNC_RESPONSE_TASK_ALLEGIANT 0x03 // Task still allegiant +#define ISCSI_TASK_MGMT_FUNC_RESPONSE_TASK_UNSUPPORTED_ALLEGIANCE 0x04 // Task allegiance reassignment not supported +#define ISCSI_TASK_MGMT_FUNC_RESPONSE_TASK_UNSUPPORTED_MGMT 0x05 // Task management function not supported +#define ISCSI_TASK_MGMT_FUNC_RESPONSE_FUNC_AUTH_FAILED 0x06 // Function authorization failed +#define ISCSI_TASK_MGMT_FUNC_RESPONSE_FUNC_REJECTED 0xFF // Function rejected + +/* For the functions ABORT TASK, ABORT TASK SET, CLEAR ACA, CLEAR TASK + SET, LOGICAL UNIT RESET, TARGET COLD RESET, TARGET WARM RESET, and + TASK REASSIGN, the target performs the requested task management + function and sends a task management response back to the initiator. + For TASK REASSIGN, the new connection allegiance MUST ONLY become + effective at the target after the target issues the task management + response. +*/ +typedef struct __attribute__((packed)) iscsi_task_mgmt_func_response_packet { + uint8_t opcode; // Always 0x22 according to specification (see above) + uint8_t flags; // Reserved for future usage + uint8_t response; // Function response (see above) + // For the TARGET COLD RESET and TARGET WARM RESET functions, the target + // cancels all pending operations across all LUs known to the issuing + // initiator. For the TARGET COLD RESET function, the target MUST then + // close all of its TCP connections to all initiators (terminates all + // sessions). + // The mapping of the response code into a SCSI service response code + // value, if needed, is outside the scope of this document. However, in + // symbolic terms, Response values 0 and 1 map to the SCSI service + // response of FUNCTION COMPLETE. Response value 2 maps to the SCSI + // service response of INCORRECT LOGICAL UNIT NUMBER. All other + // Response values map to the SCSI service response of FUNCTION + // REJECTED. If a Task Management Function Response PDU does not arrive + // before the session is terminated, the SCSI service response is + // SERVICE DELIVERY OR TARGET FAILURE. + // The response to ABORT TASK SET and CLEAR TASK SET MUST only be issued + // by the target after all of the commands affected have been received + // by the target, the corresponding task management functions have been + // executed by the SCSI target, and the delivery of all responses + // delivered until the task management function completion has been + // confirmed (acknowledged through the ExpStatSN) by the initiator on + // all connections of this session. + // For the ABORT TASK function, + // a) if the Referenced Task Tag identifies a valid task leading to a + // successful termination, then targets must return the "Function + // complete" response. + // b) if the Referenced Task Tag does not identify an existing task + // but the CmdSN indicated by the RefCmdSN field in the Task + // Management Function Request is within the valid CmdSN window + // and less than the CmdSN of the Task Management Function Request + // itself, then targets must consider the CmdSN as received and + // return the "Function complete" response. + // c) if the Referenced Task Tag does not identify an existing task + // and the CmdSN indicated by the RefCmdSN field in the Task + // Management Function Request is outside the valid CmdSN window, + // then targets must return the "Task does not exist" response + uint8_t reserved; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength (MUST be 0 for this PDU) + uint8_t ds_len[3]; // DataSegmentLength (MUST be 0 for this PDU) + uint64_t reserved2; // Reserved for future usage + uint32_t init_task_tag; // Initiator task tag + uint32_t reserved3; // Reserved for future usage + uint32_t stat_sn; // StatSN + uint32_t exp_cmd_sn; // ExpCmdSN + uint32_t max_cmd_sn; // MaxCmdSN + uint32_t reserved4; // Reserved for future usage + uint64_t reserved5; // Reserved for future usage + struct iscsi_header_digest hdr_digest; // Optional header digest +} iscsi_task_mgmt_func_response_packet; + +#define ISCSI_SCSI_DATA_OUT_DATA_IN_FLAGS_IMMEDIATE (1 << 7) // Immediately process transfer + +typedef struct __attribute__((packed)) iscsi_scsi_data_out_req_packet { + uint8_t opcode; // Always 0x02 according to specification (see above) + int8_t flags; // Flags (see above) + uint16_t reserved; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength (MUST be 0 for this PDU) + uint8_t ds_len[3]; // DataSegmentLength (MUST be 0 for this PDU) + uint64_t lun; // Logical Unit Number (LUN) or Reserved + uint32_t init_task_tag; // Initiator task tag + uint32_t target_xfer_tag; // Target transfer tag or 0xFFFFFFFF + uint32_t reserved2; // Reserved for future usage + uint32_t exp_stat_sn; // ExpStatSN + uint32_t reserved3; // Reserved for future usage + uint32_t data_sn; // DataSN + uint32_t buf_offset; // Buffer offset + uint32_t reserved4; // Reserved for future usage + struct iscsi_header_digest hdr_digest; // Optional header digest + struct iscsi_data_digest data_digest; // Optional data digest +} iscsi_scsi_data_out_req_packet; + +#define ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_STATUS (1 << 0) // (S) set to indicate that the Command Status field + // contains status. If this bit is set to 1, the + // F bit MUST also be set to 1 + +#define ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_RES_UNDERFLOW (1 << 1) // (U) set for Residual Underflow. In this case, the Residual + // Count indicates the number of bytes that were not + // transferred out of the number of bytes that were expected + // to be transferred. For a bidirectional operation, the + // Residual Count contains the residual for the write + // operation. + +#define ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_RES_OVERFLOW (1 << 2) // (O) set for Residual Overflow. In this case, the Residual + // Count indicates the number of bytes that were not + // transferred because the initiator's Expected Data + // Transfer Length was not sufficient. For a bidirectional + // operation, the Residual Count contains the residual for + // the write operation. + +#define ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_ACK (1 << 6) // (A) for sessions with ErrorRecoveryLevel=1 or higher, the target sets + // this bit to 1 to indicate that it requests a positive acknowledgment + // from the initiator for the data received. The target should use the + // A bit moderately; it MAY only set the A bit to 1 once every + // MaxBurstLength bytes, or on the last Data-In PDU that concludes the + // entire requested read data transfer for the task from the target's + // perspective, and it MUST NOT do so more frequently. The target MUST + // NOT set to 1 the A bit for sessions with ErrorRecoveryLevel=0. The + // initiator MUST ignore the A bit set to 1 for sessions with + // ErrorRecoveryLevel=0. + // On receiving a Data-In PDU with the A bit set to 1 on a session with + // ErrorRecoveryLevel greater than 0, if there are no holes in the read + // data until that Data-In PDU, the initiator MUST issue a SNACK of type + // DataACK, except when it is able to acknowledge the status for the + // task immediately via the ExpStatSN on other outbound PDUs if the + // status for the task is also received. In the latter case + // (acknowledgment through the ExpStatSN), sending a SNACK of type + // DataACK in response to the A bit is OPTIONAL, but if it is done, it + // must not be sent after the status acknowledgment through the + // ExpStatSN. If the initiator has detected holes in the read data + // prior to that Data-In PDU, it MUST postpone issuing the SNACK of type + // DataACK until the holes are filled. An initiator also MUST NOT + // acknowledge the status for the task before those holes are filled. A + // status acknowledgment for a task that generated the Data-In PDUs is + // considered by the target as an implicit acknowledgment of the Data-In + // PDUs if such an acknowledgment was requested by the target + +#define ISCSI_SCSI_DATA_IN_RESPONSE_FLAGS_FINAL (1 << 7) // (F) for outgoing data, this bit is 1 for the last PDU of unsolicited + // data or the last PDU of a sequence that answers an R2T. + // For incoming data, this bit is 1 for the last input (read) data PDU + // of a sequence. Input can be split into several sequences, each + // having its own F bit. Splitting the data stream into sequences does + // not affect DataSN counting on Data-In PDUs. It MAY be used as a + // "change direction" indication for bidirectional operations that need + // such a change. + // DataSegmentLength MUST NOT exceed MaxRecvDataSegmentLength for the + // direction it is sent, and the total of all the DataSegmentLength of + // all PDUs in a sequence MUST NOT exceed MaxBurstLength (or + // FirstBurstLength for unsolicited data). However, the number of + // individual PDUs in a sequence (or in total) may be higher than the + // ratio of MaxBurstLength (or FirstBurstLength) to + // MaxRecvDataSegmentLength (as PDUs may be limited in length by the + // capabilities of the sender). Using a DataSegmentLength of 0 may + // increase beyond what is reasonable for the number of PDUs and should + // therefore be avoided. + // For bidirectional operations, the F bit is 1 for both the end of the + // input sequences and the end of the output sequences + +typedef struct __attribute__((packed)) iscsi_scsi_data_in_response_packet { + uint8_t opcode; // Always 0x25 according to specification (see above) + int8_t flags; // Incoming data flags (see above) + // The fields StatSN, Status, and Residual Count only have meaningful + // content if the S bit is set to 1 + uint8_t reserved; // Rserved for future usage + uint8_t status; // Status or Reserved + // Status can accompany the last Data-In PDU if the command did not end + // with an exception (i.e., the status is "good status" - GOOD, + // CONDITION MET, or INTERMEDIATE-CONDITION MET). The presence of + // status (and of a residual count) is signaled via the S flag bit. + // Although targets MAY choose to send even non-exception status in + // separate responses, initiators MUST support non-exception status in + // Data-In PDUs + uint8_t total_ahs_len; // TotalAHSLength + uint8_t ds_len[3]; // DataSegmentLength + // This is the data payload length of a SCSI Data-In or SCSI Data-Out + // PDU. The sending of 0-length data segments should be avoided, but + // initiators and targets MUST be able to properly receive 0-length data + // segments. + // The data segments of Data-In and Data-Out PDUs SHOULD be filled to + // the integer number of 4-byte words (real payload), unless the F bit + // is set to 1 + uint64_t lun; // Logical Unit Number (LUN) or Reserved + // If the Target Transfer Tag isprovided, then the LUN field MUST hold a + // valid value and be consistent with whatever was specified with the command; + // otherwise, the LUN field is reserved + uint32_t init_task_tag; // Initiator task tag + uint32_t target_xfer_tag; // On outgoing data, the Target Transfer Tag is provided to the target + // if the transfer is honoring an R2T. In this case, the Target + // Transfer Tag field is a replica of the Target Transfer Tag provided + // with the R2T. + // On incoming data, the Target Transfer Tag and LUN MUST be provided by + // the target if the A bit is set to 1; otherwise, they are reserved. + // The Target Transfer Tag and LUN are copied by the initiator into the + // SNACK of type DataACK that it issues as a result of receiving a SCSI + // Data-In PDU with the A bit set to 1. + // The Target Transfer Tag values are not specified by this protocol, + // except that the value 0xFFFFFFFF is reserved and means that the + // Target Transfer Tag is not supplied + uint32_t stat_sn; // StatSN + uint32_t exp_cmd_sn; // ExpCmdSN + uint32_t max_cmd_sn; // MaxCmdSN + uint32_t data_sn; // DataSN + // For input (read) or bidirectional Data-In PDUs, the DataSN is the + // input PDU number within the data transfer for the command identified + // by the Initiator Task Tag. + // R2T and Data-In PDUs, in the context of bidirectional commands, share + // the numbering sequence. + // For output (write) data PDUs, the DataSN is the Data-Out PDU number + // within the current output sequence. Either the current output + // sequence is identified by the Initiator Task Tag (for unsolicited + // data) or it is a data sequence generated for one R2T (for data + // solicited through R2T) + uint32_t buf_offset; // Buffer Offset + // The Buffer Offset field contains the offset of this PDU payload data + // within the complete data transfer. The sum of the buffer offset and + // length should not exceed the expected transfer length for the + // command. + // The order of data PDUs within a sequence is determined by + // DataPDUInOrder. When set to Yes, it means that PDUs have to be in + // increasing buffer offset order and overlays are forbidden. + // The ordering between sequences is determined by DataSequenceInOrder. + // When set to Yes, it means that sequences have to be in increasing + // buffer offset order and overlays are forbidden + uint32_t res_cnt; // Residual Count or Reserved + struct iscsi_header_digest hdr_digest; // Optional header digest + struct iscsi_ds_cmd_data ds_cmd_data; // Data segment + struct iscsi_data_digest data_digest; // Optional data digest +} iscsi_scsi_data_in_response_packet; + +/* When an initiator has submitted a SCSI command with data that passes + from the initiator to the target (write), the target may specify + which blocks of data it is ready to receive. The target may request + that the data blocks be delivered in whichever order is convenient + for the target at that particular instant. This information is + passed from the target to the initiator in the Ready To Transfer + (R2T) PDU. + + In order to allow write operations without an explicit initial R2T, + the initiator and target MUST have negotiated the key InitialR2T to + No during login. + + An R2T MAY be answered with one or more SCSI Data-Out PDUs with a + matching Target Transfer Tag. If an R2T is answered with a single + Data-Out PDU, the buffer offset in the data PDU MUST be the same as + the one specified by the R2T, and the data length of the data PDU + MUST be the same as the Desired Data Transfer Length specified in the + R2T. If the R2T is answered with a sequence of data PDUs, the buffer + offset and length MUST be within the range of those specified by the + R2T, and the last PDU MUST have the F bit set to 1. If the last PDU + (marked with the F bit) is received before the Desired Data Transfer + Length is transferred, a target MAY choose to reject that PDU with + the "Protocol Error" reason code. DataPDUInOrder governs the + Data-Out PDU ordering. If DataPDUInOrder is set to Yes, the buffer + offsets and lengths for consecutive PDUs MUST form a continuous + non-overlapping range, and the PDUs MUST be sent in increasing offset + order. + + The target may send several R2T PDUs. It therefore can have a number + of pending data transfers. The number of outstanding R2T PDUs is + limited by the value of the negotiated key MaxOutstandingR2T. Within + a task, outstanding R2Ts MUST be fulfilled by the initiator in the + order in which they were received. + + R2T PDUs MAY also be used to recover Data-Out PDUs. Such an R2T + (Recovery-R2T) is generated by a target upon detecting the loss of + one or more Data-Out PDUs due to: + + - Digest error + + - Sequence error + + - Sequence reception timeout + + A Recovery-R2T carries the next unused R2TSN but requests part of or + the entire data burst that an earlier R2T (with a lower R2TSN) had + already requested. + + DataSequenceInOrder governs the buffer offset ordering in consecutive + R2Ts. If DataSequenceInOrder is Yes, then consecutive R2Ts MUST + refer to continuous non-overlapping ranges, except for Recovery-R2Ts. +*/ +typedef struct __attribute__((packed)) iscsi_r2t_packet { + uint8_t opcode; // Always 0x31 according to specification (see above) + uint8_t flags; // Reserved for future usage + uint16_t reserved; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength, MUST be 0 for this PDU + uint8_t ds_len[3]; // DataSegmentLength, MUST be 0 0 for this PDU + uint64_t lun; // Logical Unit Number (LUN) or Reserved + uint32_t init_task_tag; // Initiator task tag + uint32_t target_xfer_tag; // Target transfer tag + uint32_t stat_sn; // The StatSN field will contain the next StatSN. The StatSN for this + // connection is not advanced after this PDU is sent + uint32_t exp_cmd_sn; // ExpCmdSN + uint32_t max_cmd_sn; // MaxCmdSN + uint32_t data_sn; // DataSN + uint32_t r2t_sn; // R2TSN is the R2T PDU input PDU number within the command identified + // by the Initiator Task Tag. + // For bidirectional commands, R2T and Data-In PDUs share the input PDU + // numbering sequence + uint32_t buf_offset; // Buffer Offset + // The target therefore also specifies a buffer offset that + // indicates the point at which the data transfer should begin, relative + // to the beginning of the total data transfer + uint32_t des_data_xfer_len; // Desired Data Transfer Length + // The target specifies how many bytes it wants the initiator to send + // because of this R2T PDU. The target may request the data from the + // initiator in several chunks, not necessarily in the original order of + // the data. The Desired Data Transfer Length MUST NOT be 0 and MUST NOT + // exceed MaxBurstLength + struct iscsi_header_digest hdr_digest; // Optional header digest +} iscsi_r2t_packet; + +#define ISCSI_ASYNC_MSG_EVENT_SCSI_ASYNC_EVENT 0x00 // (SCSI Async Event) - a SCSI asynchronous event is reported in + // the sense data. Sense Data that accompanies the report, in + // the data segment, identifies the condition. The sending of a + // SCSI event ("asynchronous event reporting" in SCSI + // terminology) is dependent on the target support for SCSI + // asynchronous event reporting as indicated in the + // standard INQUIRY data. Its use may be enabled by + // parameters in the SCSI Control mode page +#define ISCSI_ASYNC_MSG_EVENT_LOGOUT_REQUEST 0x01 // (Logout Request) - the target requests Logout. This Async + // Message MUST be sent on the same connection as the one + // requesting to be logged out. The initiator MUST honor this + // request by issuing a Logout as early as possible but no later + // than Parameter3 seconds. The initiator MUST send a Logout + // with a reason code of "close the connection" OR "close the + // session" to close all the connections. Once this message is + // received, the initiator SHOULD NOT issue new iSCSI commands on + // the connection to be logged out. The target MAY reject any + // new I/O requests that it receives after this message with the + // reason code "Waiting for Logout". If the initiator does not + // log out in Parameter3 seconds, the target should send an Async + // PDU with iSCSI event code "Dropped the connection" if possible + // or simply terminate the transport connection. Parameter1 and + // Parameter2 are reserved +#define ISCSI_ASYNC_MSG_EVENT_CONNECT_DROP_NOTIFY 0x02 // (Connection Drop Notification) - the target indicates that it + // will drop the connection. + // The Parameter1 field indicates the CID of the connection that + // is going to be dropped. + // The Parameter2 field (Time2Wait) indicates, in seconds, the + // minimum time to wait before attempting to reconnect or + // reassign. + // The Parameter3 field (Time2Retain) indicates the maximum time + // allowed to reassign commands after the initial wait (in + // Parameter2). + // If the initiator does not attempt to reconnect and/or reassign + // the outstanding commands within the time specified by + // Parameter3, or if Parameter3 is 0, the target will terminate + // all outstanding commands on this connection. In this case, no + // other responses should be expected from the target for the + // outstanding commands on this connection. + // A value of 0 for Parameter2 indicates that reconnect can be + // attempted immediately +#define ISCSI_ASYNC_MSG_EVENT_SESSION_DROP_NOTIFY 0x03 // (Session Drop Notification) - the target indicates that it + // will drop all the connections of this session. + // The Parameter1 field is reserved. + // The Parameter2 field (Time2Wait) indicates, in seconds, the + // minimum time to wait before attempting to reconnect. + // The Parameter3 field (Time2Retain) indicates the maximum time + // allowed to reassign commands after the initial wait (in + // Parameter2). + // If the initiator does not attempt to reconnect and/or reassign + // the outstanding commands within the time specified by + // Parameter3, or if Parameter3 is 0, the session is terminated. + // In this case, the target will terminate all outstanding + // commands in this session; no other responses should be + // expected from the target for the outstanding commands in this + // session. A value of 0 for Parameter2 indicates that reconnect + // can be attempted immediately +#define ISCSI_ASYNC_MSG_EVENT_NEGOTIATION_REQUEST 0x04 // (Negotiation Request) - the target requests parameter + // negotiation on this connection. The initiator MUST honor this + // request by issuing a Text Request (that can be empty) on the + // same connection as early as possible, but no later than + // Parameter3 seconds, unless a Text Request is already pending + // on the connection, or by issuing a Logout Request. If the + // initiator does not issue a Text Request, the target may + // reissue the Asynchronous Message requesting parameter + // negotiation +#define ISCSI_ASYNC_MSG_EVENT_TASK_TERMINATION 0x05 // (Task Termination) - all active tasks for a LU with a matching + // LUN field in the Async Message PDU are being terminated. The + // receiving initiator iSCSI layer MUST respond to this message + // by taking the following steps, in order: + // - Stop Data-Out transfers on that connection for all active + // TTTs for the affected LUN quoted in the Async Message PDU. + // - Acknowledge the StatSN of the Async Message PDU via a + // NOP-Out PDU with ITT=0xFFFFFFFF (i.e., non-ping flavor), + // while copying the LUN field from the Async Message to + // NOP-Out. + // This value of AsyncEvent, however, MUST NOT be used on an + // iSCSI session unless the new TaskReporting text key was + // negotiated to FastAbort on the session +#define ISCSI_ASYNC_MSG_EVENT_VENDOR_FIRST 0xF8 // First vendor-specific iSCSI event. The AsyncVCode details the + // vendor code, and data MAY accompany the report +#define ISCSI_ASYNC_MSG_EVENT_VENDOR_LAST 0xFF // Last vendor-specific iSCSI event. The AsyncVCode details the + // vendor code, and data MAY accompany the report + +/* An Asynchronous Message may be sent from the target to the initiator + without corresponding to a particular command. The target specifies + the reason for the event and sense data. + Some Asynchronous Messages are strictly related to iSCSI, while + others are related to SCSI +*/ +typedef struct __attribute__((packed)) iscsi_async_msg_packet { + uint8_t opcode; // Always 0x32 according to specification (see above + uint8_t flags; // Reserved for future usage + uint16_t reserved; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength, MUST be 0 for this PDU + uint8_t ds_len[3]; // DataSegmentLength, MUST be 0 0 for this PDU + uint64_t lun; // The LUN field MUST be valid if AsyncEvent is 0. Otherwise, this + // field is reserved + uint32_t tag; // Tag (always 0xFFFFFFFF for now) + uint32_t reserved2; // Reserved for future usage + uint32_t stat_sn; // StatSN + // The StatSN counts this PDU as an acknowledgeable event (the StatSN is + // advanced), which allows for initiator and target state synchronization. + uint32_t exp_cmd_sn; // ExpCmdSN + uint32_t max_cmd_sn; // MaxCmdSN + uint8_t async_event; // AsyncEvent + uint8_t async_vcode; // AsyncVCode is a vendor-specific detail code that is only valid if the + // AsyncEvent field indicates a vendor-specific event. Otherwise, it is + // reserved + uint16_t param_1; // Parameter1 or Reserved + uint16_t param_2; // Parameter2 or Reserved + uint16_t param_3; // Parameter3 or Reserved + uint32_t reserved3; // Reserved for future usage + struct iscsi_header_digest hdr_digest; // Optional header digest + struct iscsi_ds_cmd_data ds_cmd_data; // Data segment + struct iscsi_data_digest data_digest; // Optional data digest +} iscsi_async_msg_packet; + +/* For a SCSI event, this data accompanies the report in the data + segment and identifies the condition. + + For an iSCSI event, additional vendor-unique data MAY accompany the + Async event. Initiators MAY ignore the data when not understood, + while processing the rest of the PDU. + + If the DataSegmentLength is not 0, the format of the DataSegment is + as follows: +*/ +typedef struct __attribute__((packed)) iscsi_sense_event_data_packet { + uint16_t sense_len; // SenseLength + // This is the length of Sense Data. When the Sense Data field is empty + // (e.g., the event is not a SCSI event), SenseLength is 0 + uint16_t sense_data[0]; // Sense Data + uint16_t event_data[0]; // iSCSI Event Data +} iscsi_sense_event_data_packet; + +#define ISCSI_TEXT_REQ_FLAGS_CONTINUE (1 << 6) // (C) When set to 1, this bit indicates that the text (set of key=value + // pairs) in this Text Request is not complete (it will be continued on + // subsequent Text Requests); otherwise, it indicates that this Text + // Request ends a set of key=value pairs. A Text Request with the C bit + // set to 1 MUST have the F bit set to 0. +#define ISCSI_TEXT_REQ_FLAGS_FINAL (1 << 7) // (F) When set to 1, this bit indicates that this is the last or only Text + // Request in a sequence of Text Requests; otherwise, it indicates that + // more Text Requests will follow. + +/* The Text Request is provided to allow for the exchange of information + and for future extensions. It permits the initiator to inform a + target of its capabilities or request some special operations. + + An initiator MUST NOT have more than one outstanding Text Request on + a connection at any given time. + + On a connection failure, an initiator must either explicitly abort + any active allegiant text negotiation task or cause such a task to be + implicitly terminated by the target. +*/ +typedef struct __attribute__((packed)) iscsi_text_req_packet { + uint8_t opcode; // Always 0x04 according to specification (see above) + int8_t flags; // Text request flags (see above) + uint16_t reserved; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength + uint8_t ds_len[3]; // DataSegmentLength + uint64_t lun; // Logical Unit Number (LUN) or Reserved + uint32_t init_task_tag; // This is the initiator-assigned identifier for this Text Request. If + // the command is sent as part of a sequence of Text Requests and + // responses, the Initiator Task Tag MUST be the same for all the + // requests within the sequence (similar to linked SCSI commands). The + // I bit for all requests in a sequence also MUST be the same + uint32_t target_xfer_tag; // When the Target Transfer Tag is set to the reserved value 0xFFFFFFFF, + // it tells the target that this is a new request, and the target resets + // any internal state associated with the Initiator Task Tag (resets the + // current negotiation state). + // The target sets the Target Transfer Tag in a Text Response to a value + // other than the reserved value 0xFFFFFFFF whenever it indicates that + // it has more data to send or more operations to perform that are + // associated with the specified Initiator Task Tag. It MUST do so + // whenever it sets the F bit to 0 in the response. By copying the + // Target Transfer Tag from the response to the next Text Request, the + // initiator tells the target to continue the operation for the specific + // Initiator Task Tag. The initiator MUST ignore the Target Transfer + // Tag in the Text Response when the F bit is set to 1. + // This mechanism allows the initiator and target to transfer a large + // amount of textual data over a sequence of text-command/text-response + // exchanges or to perform extended negotiation sequences. + // If the Target Transfer Tag is not 0xFFFFFFFF, the LUN field MUST be + // sent by the target in the Text Response. + // A target MAY reset its internal negotiation state if an exchange is + // stalled by the initiator for a long time or if it is running out of + // resources. + // Long Text Responses are handled as shown in the following example: + // I->T Text SendTargets=All (F = 1, TTT = 0xFFFFFFFF) + // T->I Text <part 1> (F = 0, TTT = 0x12345678) + // I->T Text <empty> (F = 1, TTT = 0x12345678) + // T->I Text <part 2> (F = 0, TTT = 0x12345678) + // I->T Text <empty> (F = 1, TTT = 0x12345678) + // ... + // T->I Text <part n> (F = 1, TTT = 0xFFFFFFFF) + uint32_t cmd_sn; // CmdSN + uint32_t exp_stat_sn; // ExpStatSN + uint64_t reserved2[2]; // Reserved for future usage + struct iscsi_header_digest hdr_digest; // Optional header digest + struct iscsi_ds_cmd_data ds_cmd_data; // Data segment + // The data lengths of a Text Request MUST NOT exceed the iSCSI target + // MaxRecvDataSegmentLength (a parameter that is negotiated per + // connection and per direction). + // A key=value pair can span Text Request or Text Response boundaries. + // A key=value pair can start in one PDU and continue on the next. In + // other words, the end of a PDU does not necessarily signal the end of + // a key=value pair. + // The target responds by sending its response back to the initiator. + // The response text format is similar to the request text format. The + // Text Response MAY refer to key=value pairs presented in an earlier + // Text Request, and the text in the request may refer to earlier + // responses. + // Text operations are usually meant for parameter setting/negotiations + // but can also be used to perform some long-lasting operations + struct iscsi_data_digest data_digest; // Optional data digest +} iscsi_text_req_packet; + +#define ISCSI_TEXT_RESPONSE_FLAGS_CONTINUE (1 << 6) // (C) When set to 1, this bit indicates that the text (set of key=value + // pairs) in this Text Response is not complete (it will be continued on + // subsequent Text Responses); otherwise, it indicates that this Text + // Response ends a set of key=value pairs. A Text Response with the + // C bit set to 1 MUST have the F bit set to 0 +#define ISCSI_TEXT_RESPONSE_FLAGS_FINAL (1 << 7) // (F) When set to 1, in response to a Text Request with the Final bit set + // to 1, the F bit indicates that the target has finished the whole + // operation. Otherwise, if set to 0 in response to a Text Request with + // the Final Bit set to 1, it indicates that the target has more work to + // do (invites a follow-on Text Request). A Text Response with the + // F bit set to 1 in response to a Text Request with the F bit set to 0 + // is a protocol error. + // A Text Response with the F bit set to 1 MUST NOT contain key=value + // pairs that may require additional answers from the initiator. + // A Text Response with the F bit set to 1 MUST have a Target Transfer + // Tag field set to the reserved value 0xFFFFFFFF. + // A Text Response with the F bit set to 0 MUST have a Target Transfer + // Tag field set to a value other than the reserved value 0xFFFFFFFF + +/* The Text Response PDU contains the target's responses to the + initiator's Text Request. The format of the Text field matches that + of the Text Request. +*/ +typedef struct __attribute__((packed)) iscsi_text_response_packet { + uint8_t opcode; // Always 0x24 according to specification (see above) + int8_t flags; // Text response flags (see above) + uint16_t reserved; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength + uint8_t ds_len[3]; // DataSegmentLength + uint64_t lun; // Logical Unit Number (LUN) or Reserved + uint32_t init_task_tag; // The Initiator Task Tag matches the tag used in the initial Text Request + uint32_t target_xfer_tag; // When a target has more work to do (e.g., cannot transfer all the + // remaining text data in a single Text Response or has to continue the + // negotiation) and has enough resources to proceed, it MUST set the + // Target Transfer Tag to a value other than the reserved value + // 0xFFFFFFFF. Otherwise, the Target Transfer Tag MUST be set to + // 0xFFFFFFFF. + // When the Target Transfer Tag is not 0xFFFFFFFF, the LUN field may be + // significant. + // The initiator MUST copy the Target Transfer Tag and LUN in its next + // request to indicate that it wants the rest of the data. + // When the target receives a Text Request with the Target Transfer Tag + // set to the reserved value 0xFFFFFFFF, it resets its internal + // information (resets state) associated with the given Initiator Task + // Tag (restarts the negotiation). + // When a target cannot finish the operation in a single Text Response + // and does not have enough resources to continue, it rejects the Text + // Request with the appropriate Reject code. + // A target may reset its internal state associated with an Initiator + // Task Tag (the current negotiation state) as expressed through the + // Target Transfer Tag if the initiator fails to continue the exchange + // for some time. The target may reject subsequent Text Requests with + // the Target Transfer Tag set to the "stale" value + uint32_t stat_sn; // StatSN. The target StatSN variable is advanced by each Text Response sent + uint32_t exp_cmd_sn; // ExpCmdSN + uint32_t max_cmd_sn; // MaxCmdSN + uint64_t reserved2[2]; // Reserved for future usage + struct iscsi_header_digest hdr_digest; // Optional header digest + struct iscsi_ds_cmd_data ds_cmd_data; // Data segment + // The data lengths of a Text Response MUST NOT exceed the iSCSI + // initiator MaxRecvDataSegmentLength (a parameter that is negotiated + // per connection and per direction). + // The text in the Text Response Data is governed by the same rules as + // the text in the Text Request Data. + // Although the initiator is the requesting party and controls the + // request-response initiation and termination, the target can offer + // key=value pairs of its own as part of a sequence and not only in + // response to the initiator + struct iscsi_data_digest data_digest; // Optional data digest +} iscsi_text_response_packet; + +#define ISCSI_ISID_TYPE_BITS (1 << 6) // Two bits - The T field identifies the format and usage of A, B, C, and D as + // indicated below: +#define ISCSI_ISID_TYPE_FORMAT_OUI 0x0 // OUI-Format + // A and B: 22-bit OUI + // (the I/G and U/L bits are omitted) + // C and D: 24-bit Qualifier +#define ISCSI_ISID_TYPE_FORMAT_EN 0x1 // EN: Format (IANA Enterprise Number) + // A: Reserved + // B and C: EN (IANA Enterprise Number) + // D: Qualifier +#define ISCSI_ISID_TYPE_FORMAT_RANDOM 0x2 // Random + // A: Reserved + // B and C: Random + // D: Qualifier + +/* Only the following keys are used during the SecurityNegotiation stage + of the Login Phase (other keys MUST NOT be used): +*/ +#define ISCSI_LOGIN_AUTH_TEXT_KEY_SESSION_TYPE "SessionType" // Use: LO, Declarative, Any-Stage + // Senders: Initiator + // Scope: SW + // SessionType=<Discovery|Normal> + // Default is Normal. + // The initiator indicates the type of session it wants to create. The + // target can either accept it or reject it. + // A Discovery session indicates to the target that the only purpose of + // this session is discovery. The only requests a target accepts in + // this type of session are a Text Request with a SendTargets key and a + // Logout Request with reason "close the session". + // The Discovery session implies MaxConnections = 1 and overrides both + // the default and an explicit setting. ErrorRecoveryLevel MUST be 0 + // (zero) for Discovery sessions. + // Depending on the type of session, a target may decide on resources to + // allocate, the security to enforce, etc., for the session. If the + // SessionType key is thus going to be offered as "Discovery", it SHOULD + // be offered in the initial Login Request by the initiator +#define ISCSI_LOGIN_AUTH_TEXT_KEY_INITIATOR_NAME "InitiatorName" // Use: IO, Declarative, Any-Stage + // Senders: Initiator + // Scope: SW + // InitiatorName=<iSCSI-name-value> + // Examples: + // InitiatorName=iqn.1992-04.de.uni-freiburg.bwlehrpool:qcow2.5003 + // InitiatorName=iqn.2001-02.de.uni-freiburg.matrix:basty.eduroam + // InitiatorName=naa.52004567BA64678D + // The initiator of the TCP connection MUST provide this key to the + // remote endpoint at the first login of the Login Phase for every + // connection. The InitiatorName key enables the initiator to identify + // itself to the remote endpoint. + // The InitiatorName MUST NOT be redeclared within the Login Phase +#define ISCSI_LOGIN_AUTH_TEXT_KEY_TARGET_NAME "TargetName" // Use: IO by initiator, FFPO by target - only as response to a + // SendTargets, Declarative, Any-Stage + // Senders: Initiator and target + // Scope: SW + // TargetName=<iSCSI-name-value> + // Examples: + // TargetName=iqn.1993-11.de.uni-freiburg:diskarrays.sn.5003 + // TargetName=eui.020000023B040506 + // TargetName=naa.62004567BA64678D0123456789ABCDEF + // The initiator of the TCP connection MUST provide this key to the + // remote endpoint in the first Login Request if the initiator is not + // establishing a Discovery session. The iSCSI Target Name specifies + // the worldwide unique name of the target. + // The TargetName key may also be returned by the SendTargets Text + // Request (which is its only use when issued by a target). + // The TargetName MUST NOT be redeclared within the Login Phase +#define ISCSI_LOGIN_AUTH_TEXT_KEY_TARGET_ADDRESS "TargetAddress" // Use: ALL, Declarative, Any-Stage + // Senders: Target + // Scope: SW + // TargetAddress=domainname[:port][,portal-group-tag] + // The domainname can be specified as either a DNS host name, a dotted- + // decimal IPv4 address, or a bracketed IPv6 address as specified in + // RFC3986. + // If the TCP port is not specified, it is assumed to be the IANA- + // assigned default port for iSCSI. + // If the TargetAddress is returned as the result of a redirect status + // in a Login Response, the comma and portal-group-tag MUST be omitted. + // If the TargetAddress is returned within a SendTargets response, the + // portal-group-tag MUST be included. + // Examples: + // TargetAddress=10.0.0.1:5003,1 + // TargetAddress=[1080:0:0:0:8:800:200C:417A],65 + // TargetAddress=[1080::8:800:200C:417A]:5003,1 + // TargetAddress=gitlab.uni-freiburg.de,443 + // The formats for the port and portal-group-tag are the same as the one + // specified in TargetPortalGroupTag +#define ISCSI_LOGIN_AUTH_TEXT_KEY_INITIATOR_ALIAS "InitiatorAlias" // Use: ALL, Declarative, Any-Stage + // Senders: Initiator + // Scope: SW + // InitiatorAlias=<iSCSI-local-name-value> + // Examples: + // InitiatorAlias=Web Server 5 + // InitiatorAlias=matrix.uni-freiburg.de + // InitiatorAlias=Matrix Server + // If an initiator has been configured with a human-readable name or + // description, it SHOULD be communicated to the target during a Login + // Request PDU. If not, the host name can be used instead. This string + // is not used as an identifier, nor is it meant to be used for + // authentication or authorization decisions. It can be displayed by + // the target's user interface in a list of initiators to which it is + // connected +#define ISCSI_LOGIN_AUTH_TEXT_KEY_TARGET_ALIAS "TargetAlias" // Use: ALL, Declarative, Any-Stage + // Senders: Target + // Scope: SW + // TargetAlias=<iSCSI-local-name-value> + // Examples: + // TargetAlias=Bob-s Disk + // TargetAlias=Database Server 1 Log Disk + // TargetAlias=Web Server 3 Disk 20 + // If a target has been configured with a human-readable name or + // description, this name SHOULD be communicated to the initiator during + // a Login Response PDU if SessionType=Normal. This string is not used + // as an identifier, nor is it meant to be used for authentication or + // authorization decisions. It can be displayed by the initiator's user + // interface in a list of targets to which it is connected +#define ISCSI_LOGIN_AUTH_TEXT_KEY_TARGET_PORTAL_GROUP_TAG "TargetPortalGroupTag" // Use: IO by target, Declarative, Any-Stage + // Senders: Target + // Scope: SW + // TargetPortalGroupTag=<16-bit-binary-value> + // Example: + // TargetPortalGroupTag=1 + // The TargetPortalGroupTag key is a 16-bit binary-value that uniquely + // identifies a portal group within an iSCSI target node. This key + // carries the value of the tag of the portal group that is servicing + // the Login Request. The iSCSI target returns this key to the + // initiator in the Login Response PDU to the first Login Request PDU + // that has the C bit set to 0 when TargetName is given by the + // initiator. + // SAM2 notes in its informative text that the TPGT value should be + // non-zero; note that this is incorrect. A zero value is allowed as a + // legal value for the TPGT. This discrepancy currently stands + // corrected in SAM4 +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD "AuthMethod" // Use: During Login - Security Negotiation + // Senders: Initiator and target + // Scope: connection + // AuthMethod = <list-of-values> + // The main item of security negotiation is the authentication method + // (AuthMethod). + // The authentication methods that can be used (appear in the list-of- + // values) are either vendor-unique methods or those listed in the + // following table: + // +--------------------------------------------------------------+ + // | Name | Description | + // +--------------------------------------------------------------+ + // | KRB5 | Kerberos V5 - defined in RFC4120 | + // +--------------------------------------------------------------+ + // | SRP | Secure Remote Password - | + // | | defined in RFC2945 | + // +--------------------------------------------------------------+ + // | CHAP | Challenge Handshake Authentication Protocol - | + // | | defined in RFC1994 | + // +--------------------------------------------------------------+ + // | None | No authentication | + // +--------------------------------------------------------------+ + // The AuthMethod selection is followed by an "authentication exchange" + // specific to the authentication method selected. + // The authentication method proposal may be made by either the + // initiator or the target. However, the initiator MUST make the first + // step specific to the selected authentication method as soon as it is + // selected. It follows that if the target makes the authentication + // method proposal, the initiator sends the first key(s) of the exchange + // together with its authentication method selection. + // The authentication exchange authenticates the initiator to the target + // and, optionally, the target to the initiator. Authentication is + // OPTIONAL to use but MUST be supported by the target and initiator. + // The initiator and target MUST implement CHAP. All other + // authentication methods are OPTIONAL. + // Private or public extension algorithms MAY also be negotiated for + // authentication methods. Whenever a private or public extension + // algorithm is part of the default offer (the offer made in the absence + // of explicit administrative action), the implementer MUST ensure that + // CHAP is listed as an alternative in the default offer and "None" is + // not part of the default offer. + // Extension authentication methods MUST be named using one of the + // following two formats: + // 1) Z-reversed.vendor.dns_name.do_something= + // 2) New public key with no name prefix constraints + // Authentication methods named using the Z- format are used as private + // extensions. New public keys must be registered with IANA using the + // IETF Review process RFC5226. New public extensions for + // authentication methods MUST NOT use the Z# name prefix. + // For all of the public or private extension authentication methods, + // the method-specific keys MUST conform to the format specified for + // standard-label. + // To identify the vendor for private extension authentication methods, + // we suggest using the reversed DNS-name as a prefix to the proper + // digest names. + // The part of digest-name following Z- MUST conform to the format for + // standard-label. + // Support for public or private extension authentication methods is + // OPTIONAL + +/* Kerberos V5 (KRB5) related authentication keys: */ +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_KRB_AP_REQ "KRB_AP_REQ" // For KRB5 (Kerberos V5) (see RFC4120 and RFC1964), the initiator MUST use: + // KRB_AP_REQ=<KRB_AP_REQ> + // where KRB_AP_REQ is the client message as defined in RFC4120. + // The default principal name assumed by an iSCSI initiator or target + // (prior to any administrative configuration action) MUST be the iSCSI + // Initiator Name or iSCSI Target Name, respectively, prefixed by the + // string "iscsi/". + // If the initiator authentication fails, the target MUST respond with a + // Login reject with "Authentication Failure" status. Otherwise, if the + // initiator has selected the mutual authentication option (by setting + // MUTUAL-REQUIRED in the ap-options field of the KRB_AP_REQ), the + // target MUST reply with: +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_KRB_AP_REP "KRB_AP_REP" // KRB_AP_REP=<KRB_AP_REP> + // where KRB_AP_REP is the server's response message as defined in + // RFC4120. + // If mutual authentication was selected and target authentication + // fails, the initiator MUST close the connection. + // KRB_AP_REQ and KRB_AP_REP are binary-values, and their binary length + // (not the length of the character string that represents them in + // encoded form) MUST NOT exceed 65536 bytes. Hex or Base64 encoding + // may be used for KRB_AP_REQ and KRB_AP_REP + +/* Secure Remote Password (SRP) related authentication keys: */ +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_SRP_SRP_U "SRP_U" // For SRP RFC2945, the initiator MUST use: + // SRP_U=<U> TargetAuth=Yes or TargetAuth=No + // The target MUST answer with a Login reject with the "Authorization + // Failure" status or reply with: +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_SRP_SRP_GROUP "SRP_GROUP" // SRP_GROUP=<G1,G2...> SRP_s=<s> + // where G1,G2... are proposed groups, in order of preference. +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_SRP_SRP_A "SRP_A" // The initiator MUST either close the connection or continue with: + // SRP_A=<A> + // SRP_GROUP=<G> + // where G is one of G1,G2... that were proposed by the target. + // The target MUST answer with a Login reject with the "Authentication + // Failure" status or reply with: +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_SRP_SRP_B "SRP_B" // SRP_B=<B> + // The initiator MUST close the connection or continue with: +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_SRP_SRP_M "SRP_M" // SRP_M=<M> + // If the initiator authentication fails, the target MUST answer with a + // Login reject with "Authentication Failure" status. Otherwise, if the + // initiator sent TargetAuth=Yes in the first message (requiring target + // authentication), the target MUST reply with: +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_SRP_SRP_HM "SRP_HM" // SRP_HM=<H(A | M | K)> + // If the target authentication fails, the initiator MUST close the + // connection: + // where U, s, A, B, M, and H(A | M | K) are defined in RFC2945 (using + // the SHA1 hash function, such as SRP-SHA1) and + // G,Gn ("Gn" stands for G1,G2...) are identifiers of SRP groups + // specified in RFC3723. + // G, Gn, and U are text strings; s,A,B,M, and H(A | M | K) are + // binary-values. The length of s,A,B,M and H(A | M | K) in binary form + // (not the length of the character string that represents them in + // encoded form) MUST NOT exceed 1024 bytes. Hex or Base64 encoding may + // be used for s,A,B,M and H(A | M | K). + // For the SRP_GROUP, all the groups specified in RFC3723 up to + // 1536 bits (i.e. SRP-768, SRP-1024, SRP-1280, SRP-1536) must be + // supported by initiators and targets. To guarantee interoperability, + // targets MUST always offer "SRP-1536" as one of the proposed groups + +/* Challenge Handshake Authentication Protocol (CHAP) related authentication keys: */ +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_CHAP_CHAP_A "CHAP_A" // For CHAP RFC1994, the initiator MUST use: + // CHAP_A=<A1,A2...> + // where A1,A2... are proposed algorithms, in order of preference. + // The target MUST answer with a Login reject with the "Authentication + // Failure" status or reply with: + // CHAP_A=<A> +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_CHAP_CHAP_I "CHAP_I" // CHAP_I=<I> +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_CHAP_CHAP_C "CHAP_C" // CHAP_C=<C> + // where A is one of A1,A2... that were proposed by the initiator. + // The initiator MUST continue with: +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_CHAP_CHAP_N "CHAP_N" // CHAP_N=<N> +#define ISCSI_LOGIN_AUTH_TEXT_KEY_AUTH_METHOD_CHAP_CHAP_R "CHAP_R" // CHAP_R=<R> + // or, if it requires target authentication, with: + // CHAP_N=<N> + // CHAP_R=<R> + // CHAP_I=<I> + // CHAP_C=<C> + // If the initiator authentication fails, the target MUST answer with a + // Login reject with "Authentication Failure" status. Otherwise, if the + // initiator required target authentication, the target MUST either + // answer with a Login reject with "Authentication Failure" or reply + // with: + // CHAP_N=<N> + // CHAP_R=<R> + // If the target authentication fails, the initiator MUST close the + // connection: + // where N, (A,A1,A2), I, C, and R are (correspondingly) the Name, + // Algorithm, Identifier, Challenge, and Response as defined in + // RFC1994. + // N is a text string; A,A1,A2, and I are numbers; C and R are + // binary-values. Their binary length (not the length of the character + // string that represents them in encoded form) MUST NOT exceed + // 1024 bytes. Hex or Base64 encoding may be used for C and R. + // For the Algorithm, as stated in [RFC1994], one value is required to + // be implemented: + // 5 (CHAP with MD5) + // To guarantee interoperability, initiators MUST always offer it as one + // of the proposed algorithms + +/* Login/Text Operational Text Keys + + Some session-specific parameters MUST only be carried on the leading + connection and cannot be changed after the leading connection login + (e.g., MaxConnections - the maximum number of connections). This + holds for a single connection session with regard to connection + restart. The keys that fall into this category have the "use: LO" + (Leading Only). + + Keys that can only be used during login have the "use: IO" + (Initialize Only), while those that can be used in both the Login + Phase and Full Feature Phase have the "use: ALL". + + Keys that can only be used during the Full Feature Phase use FFPO + (Full Feature Phase Only). + + Keys marked as Any-Stage may also appear in the SecurityNegotiation + stage, while all other keys described in this section are + operational keys. + + Keys that do not require an answer are marked as Declarative. + + Key scope is indicated as session-wide (SW) or connection-only (CO). + + "Result function", wherever mentioned, states the function that can + be applied to check the validity of the responder selection. + "Minimum" means that the selected value cannot exceed the offered + value. "Maximum" means that the selected value cannot be lower than + the offered value. "AND" means that the selected value must be a + possible result of a Boolean "and" function with an arbitrary Boolean + value (e.g., if the offered value is No the selected value must be + No). "OR" means that the selected value must be a possible result of + a Boolean "or" function with an arbitrary Boolean value (e.g., if the + offered value is Yes the selected value must be Yes). +*/ +#define ISCSI_LOGIN_AUTH_TEXT_KEY_HEADER_DIGEST "HeaderDigest" // Use: IO +#define ISCSI_LOGIN_AUTH_TEXT_KEY_DATA_DIGEST "DataDigest" // Senders: Initiator and target + // Scope: CO + // HeaderDigest = <list-of-values> + // DataDigest = <list-of-values> + // Default is None for both HeaderDigest and DataDigest. + // Digests enable the checking of end-to-end, non-cryptographic data + // integrity beyond the integrity checks provided by the link layers and + // the covering of the whole communication path, including all elements + // that may change the network-level PDUs, such as routers, switches, + // and proxies. + // The following table lists cyclic integrity checksums that can be + // negotiated for the digests and MUST be implemented by every iSCSI + // initiator and target. These digest options only have error detection + // significance. + // +---------------------------------------------+ + // | Name | Description | Generator | + // +---------------------------------------------+ + // | CRC32C | 32-bit CRC |0x11edc6f41| + // +---------------------------------------------+ + // | None | no digest | + // +---------------------------------------------+ + // The generator polynomial G(x) for this digest is given in hexadecimal + // notation (e.g. "0x3b" stands for 0011 1011, and the polynomial is + // x**5 + x**4 + x**3 + x + 1). + // When the initiator and target agree on a digest, this digest MUST be + // used for every PDU in the Full Feature Phase. + // Padding bytes, when present in a segment covered by a CRC, SHOULD be + // set to 0 and are included in the CRC. + // The CRC MUST be calculated by a method that produces the same results + // as the following process: + // - The PDU bits are considered as the coefficients of a polynomial + // M(x) of degree n - 1; bit 7 of the lowest numbered byte is + // considered the most significant bit (x**n - 1), followed by bit 6 + // of the lowest numbered byte through bit 0 of the highest numbered + // byte (x**0). + // - The most significant 32 bits are complemented. + // - The polynomial is multiplied by x**32, then divided by G(x). The + // generator polynomial produces a remainder R(x) of degree <= 31. + // - The coefficients of R(x) are formed into a 32-bit sequence. + // - The bit sequence is complemented, and the result is the CRC. + // - The CRC bits are mapped into the digest word. The x**31 + // coefficient is mapped to bit 7 of the lowest numbered byte of the + // digest, and the mapping continues with successive coefficients and + // bits so that the x**24 coefficient is mapped to bit 0 of the lowest + // numbered byte. The mapping continues further with the x**23 + // coefficient mapped to bit 7 of the next byte in the digest until + // the x**0 coefficient is mapped to bit 0 of the highest numbered + // byte of the digest. + // - Computing the CRC over any segment (data or header) extended to + // include the CRC built using the generator 0x11edc6f41 will always + // get the value 0x1c2d19ed as its final remainder (R(x)). This value + // is given here in its polynomial form (i.e., not mapped as the + // digest word). + // For a discussion about selection criteria for the CRC, see RFC3385. + // For a detailed analysis of the iSCSI polynomial, see Castagnoli93. + // Private or public extension algorithms MAY also be negotiated for + // digests. Whenever a private or public digest extension algorithm is + // part of the default offer (the offer made in the absence of explicit + // administrative action), the implementer MUST ensure that CRC32C is + // listed as an alternative in the default offer and "None" is not part + // of the default offer. + // Extension digest algorithms MUST be named using one of the following + // two formats: + // 1) Y-reversed.vendor.dns_name.do_something= + // 2) New public key with no name prefix constraints + // Digests named using the Y- format are used for private purposes + // (unregistered). New public keys must be registered with IANA using + // the IETF Review process (RFC5226). New public extensions for + // digests MUST NOT use the Y# name prefix. + // For private extension digests, to identify the vendor we suggest + // using the reversed DNS-name as a prefix to the proper digest names. + // The part of digest-name following Y- MUST conform to the format for + // standard-label specified. + // Support for public or private extension digests is OPTIONA +#define ISCSI_LOGIN_AUTH_TEXT_KEY_MAX_CONNECTIONS "MaxConnections" // Use: LO + // Senders: Initiator and target + // Scope: SW + // Irrelevant when: SessionType=Discovery + // MaxConnections=<numerical-value-from-1-to-65535> + // Default is 1. + // Result function is Minimum. + // The initiator and target negotiate the maximum number of connections + // requested/acceptable +#define ISCSI_LOGIN_AUTH_TEXT_KEY_SEND_TARGETS "SendTargets" // Use: FFPO + // Senders: Initiator + // Scope: SW + // The text in this appendix is a normative part of this document. + // To reduce the amount of configuration required on an initiator, iSCSI + // provides the SendTargets Text Request. The initiator uses the + // SendTargets request to get a list of targets to which it may have + // access, as well as the list of addresses (IP address and TCP port) on + // which these targets may be accessed. + // To make use of SendTargets, an initiator must first establish one of + // two types of sessions. If the initiator establishes the session + // using the key "SessionType=Discovery", the session is a Discovery + // session, and a target name does not need to be specified. Otherwise, + // the session is a Normal operational session. The SendTargets command + // MUST only be sent during the Full Feature Phase of a Normal or + // Discovery session. + // A system that contains targets MUST support Discovery sessions on + // each of its iSCSI IP address-port pairs and MUST support the + // SendTargets command on the Discovery session. In a Discovery + // session, a target MUST return all path information (IP address-port + // pairs and Target Portal Group Tags) for the targets on the target + // Network Entity that the requesting initiator is authorized to access. + // A target MUST support the SendTargets command on operational + // sessions; these will only return path information about the target to + // which the session is connected and do not need to return information + // about other target names that may be defined in the responding + // system. + // An initiator MAY make use of the SendTargets command as it sees fit. + // A SendTargets command consists of a single Text Request PDU. This + // PDU contains exactly one text key and value. The text key MUST be + // SendTargets. The expected response depends upon the value, as well + // as whether the session is a Discovery session or an operational + // session. + // The value must be one of: + // All + // The initiator is requesting that information on all relevant + // targets known to the implementation be returned. This value + // MUST be supported on a Discovery session and MUST NOT be + // supported on an operational session. + // <iSCSI-target-name> + // If an iSCSI Target Name is specified, the session should + // respond with addresses for only the named target, if possible. + // This value MUST be supported on Discovery sessions. A + // Discovery session MUST be capable of returning addresses for + // those targets that would have been returned had value=All been + // designated. + // <nothing> + // The session should only respond with addresses for the target + // to which the session is logged in. This MUST be supported on + // operational sessions and MUST NOT return targets other than the + // one to which the session is logged in. + // The response to this command is a Text Response that contains a list + // of zero or more targets and, optionally, their addresses. Each + // target is returned as a target record. A target record begins with + // the TargetName text key, followed by a list of TargetAddress text + // keys, and bounded by the end of the Text Response or the next + // TargetName key, which begins a new record. No text keys other than + // TargetName and TargetAddress are permitted within a SendTargets + // response. + // A Discovery session MAY respond to a SendTargets request with its + // complete list of targets, or with a list of targets that is based on + // the name of the initiator logged in to the session. + // A SendTargets response MUST NOT contain target names if there are no + // targets for the requesting initiator to access. + // Each target record returned includes zero or more TargetAddress + // fields. + // Each target record starts with one text key of the form: + // TargetName=<target-name-goes-here> + // followed by zero or more address keys of the form: + // TargetAddress=<hostname-or-ipaddress>[:<tcp-port>], + // <portal-group-tag> + // The hostname-or-ipaddress contains a domain name, IPv4 address, or + // IPv6 address (RFC4291), as specified for the TargetAddress key. + // A hostname-or-ipaddress duplicated in TargetAddress responses for a + // given node (the port is absent or equal) would probably indicate that + // multiple address families are in use at once (IPv6 and IPv4). + // Each TargetAddress belongs to a portal group, identified by its + // numeric Target Portal Group Tag. The iSCSI Target + // Name, together with this tag, constitutes the SCSI port identifier; + // the tag only needs to be unique within a given target's name list of + // addresses. + // Multiple-connection sessions can span iSCSI addresses that belong to + // the same portal group. + // Multiple-connection sessions cannot span iSCSI addresses that belong + // to different portal groups. + // If a SendTargets response reports an iSCSI address for a target, it + // SHOULD also report all other addresses in its portal group in the + // same response. + // A SendTargets Text Response can be longer than a single Text Response + // PDU and makes use of the long Text Responses as specified. + // After obtaining a list of targets from the Discovery session, an + // iSCSI initiator may initiate new sessions to log in to the discovered + // targets for full operation. The initiator MAY keep the Discovery + // session open and MAY send subsequent SendTargets commands to discover + // new targets. + // Examples: + // This example is the SendTargets response from a single target that + // has no other interface ports. + // The initiator sends a Text Request that contains: + // SendTargets=All + // The target sends a Text Response that contains: + // TargetName=iqn.1993-11.de.uni-freiburg:diskarray.sn.8675309 + // All the target had to return in this simple case was the target name. + // It is assumed by the initiator that the IP address and TCP port for + // this target are the same as those used on the current connection to + // the default iSCSI target. + // The next example has two internal iSCSI targets, each accessible via + // two different ports with different IP addresses. The following is + // the Text Response: + // TargetName=iqn.1993-11.de.uni-freiburg:diskarray.sn.8675309 + // TargetAddress=10.1.0.45:5300,1 + // TargetAddress=10.1.1.45:5300,2 + // TargetName=iqn.1993-11.de.uni-freiburg:diskarray.sn.1234567 + // TargetAddress=10.1.0.45:5300,1 + // TargetAddress=10.1.1.45:5300,2 + // Both targets share both addresses; the multiple addresses are likely + // used to provide multi-path support. The initiator may connect to + // either target name on either address. Each of the addresses has its + // own Target Portal Group Tag; they do not support spanning multiple- + // connection sessions with each other. Keep in mind that the Target + // Portal Group Tags for the two named targets are independent of one + // another; portal group "1" on the first target is not necessarily the + // same as portal group "1" on the second target. + // In the above example, a DNS host name or an IPv6 address could have + // been returned instead of an IPv4 address. + // The next Text Response shows a target that supports spanning sessions + // across multiple addresses and further illustrates the use of the + // Target Portal Group Tags: + // TargetName=iqn.1993-11.de.uni-freiburg:diskarray.sn.8675309 + // TargetAddress=10.1.0.45:5300,1 + // TargetAddress=10.1.1.46:5300,1 + // TargetAddress=10.1.0.47:5300,2 + // TargetAddress=10.1.1.48:5300,2 + // TargetAddress=10.1.1.49:5300,3 + // In this example, any of the target addresses can be used to reach the + // same target. A single-connection session can be established to any + // of these TCP addresses. A multiple-connection session could span + // addresses .45 and .46 or .47 and .48 but cannot span any other + // combination. A TargetAddress with its own tag (.49) cannot be + // combined with any other address within the same session. + // This SendTargets response does not indicate whether .49 supports + // multiple connections per session; it is communicated via the + // MaxConnections text key upon login to the target +#define ISCSI_LOGIN_AUTH_TEXT_KEY_INITIAL_R2T "InitialR2T" // Use: LO + // Senders: Initiator and target + // Scope: SW + // Irrelevant when: SessionType=Discovery + // InitialR2T=<boolean-value> + // Examples: + // I->InitialR2T=No + // T->InitialR2T=No + // Default is Yes. + // Result function is OR. + // The InitialR2T key is used to turn off the default use of R2T for + // unidirectional operations and the output part of bidirectional + // commands, thus allowing an initiator to start sending data to a + // target as if it has received an initial R2T with Buffer + // Offset=Immediate Data Length and Desired Data Transfer + // Length=(min(FirstBurstLength, Expected Data Transfer Length) - + // Received Immediate Data Length). + // The default action is that R2T is required, unless both the initiator + // and the target send this key-pair attribute specifying InitialR2T=No. + // Only the first outgoing data burst (immediate data and/or separate + // PDUs) can be sent unsolicited (i.e., not requiring an explicit R2T) +#define ISCSI_LOGIN_AUTH_TEXT_KEY_IMMEDIATE_DATA "ImmediateData" // Use: LO + // Senders: Initiator and target + // Scope: SW + // Irrelevant when: SessionType=Discovery + // ImmediateData=<boolean-value> + // Default is Yes. + // Result function is AND. + // The initiator and target negotiate support for immediate dataTo + // turn immediate data off, the initiator or target must state its + // desire to do soImmediateData can be turned on if both the + // initiator and target have ImmediateData=Yes. + // If ImmediateData is set to Yes and InitialR2T is set to Yes + // (default), then only immediate data are accepted in the first burst. + // If ImmediateData is set to No and InitialR2T is set to Yes, then the + // initiator MUST NOT send unsolicited data and the target MUST reject + // unsolicited data with the corresponding response code. + // If ImmediateData is set to No and InitialR2T is set to No, then the + // initiator MUST NOT send unsolicited immediate data but MAY send one + // unsolicited burst of Data-OUT PDUs. + // If ImmediateData is set to Yes and InitialR2T is set to No, then the + // initiator MAY send unsolicited immediate data and/or one unsolicited + // burst of Data-OUT PDUs. + // The following table is a summary of unsolicited data options: + // +----------+-------------+------------------+-------------+ + // |InitialR2T|ImmediateData| Unsolicited |ImmediateData| + // | | | Data-Out PDUs | | + // +----------+-------------+------------------+-------------+ + // | No | No | Yes | No | + // +----------+-------------+------------------+-------------+ + // | No | Yes | Yes | Yes | + // +----------+-------------+------------------+-------------+ + // | Yes | No | No | No | + // +----------+-------------+------------------+-------------+ + // | Yes | Yes | No | Yes | + // +----------+-------------+------------------+-------------+ +#define ISCSI_LOGIN_AUTH_TEXT_KEY_MAX_RECV_DS_LEN "MaxRecvDataSegmentLength" // Use: ALL, Declarative + // Senders: Initiator and target + // Scope: CO + // MaxRecvDataSegmentLength=<numerical-value-512-to-(2**24 - 1)> + // Default is 8192 bytes. + // The initiator or target declares the maximum data segment length in + // bytes it can receive in an iSCSI PDU. + // The transmitter (initiator or target) is required to send PDUs with a + // data segment that does not exceed MaxRecvDataSegmentLength of the + // receiver. + // A target receiver is additionally limited by MaxBurstLength for + // solicited data and FirstBurstLength for unsolicited dataAn + // initiator MUST NOT send solicited PDUs exceeding MaxBurstLength nor + // unsolicited PDUs exceeding FirstBurstLength (or FirstBurstLength- + // Immediate Data Length if immediate data were sent) +#define ISCSI_LOGIN_AUTH_TEXT_KEY_MAX_BURST_LEN "MaxBurstLength" // Use: LO + // Senders: Initiator and target + // Scope: SW + // Irrelevant when: SessionType=Discovery + // MaxBurstLength=<numerical-value-512-to-(2**24 - 1)> + // Default is 262144 (256 KB). + // Result function is Minimum. + // The initiator and target negotiate the maximum SCSI data payload in + // bytes in a Data-In or a solicited Data-Out iSCSI sequence. A + // sequence consists of one or more consecutive Data-In or Data-Out PDUs + // that end with a Data-In or Data-Out PDU with the F bit set to 1 +#define ISCSI_LOGIN_AUTH_TEXT_KEY_FIRST_BURST_LEN "FirstBurstLength" // Use: LO + // Senders: Initiator and target + // Scope: SW + // Irrelevant when: SessionType=Discovery + // Irrelevant when: ( InitialR2T=Yes and ImmediateData=No ) + // FirstBurstLength=<numerical-value-512-to-(2**24 - 1)> + // Default is 65536 (64 KB). + // Result function is Minimum. + // The initiator and target negotiate the maximum amount in bytes of + // unsolicited data an iSCSI initiator may send to the target during the + // execution of a single SCSI command. This covers the immediate data + // (if any) and the sequence of unsolicited Data-Out PDUs (if any) that + // follow the command. + // FirstBurstLength MUST NOT exceed MaxBurstLength +#define ISCSI_LOGIN_AUTH_TEXT_KEY_DEFAULT_TIME_WAIT "DefaultTime2Wait" // Use: LO + // Senders: Initiator and target + // Scope: SW + // DefaultTime2Wait=<numerical-value-0-to-3600> + // Default is 2. + // Result function is Maximum. + // The initiator and target negotiate the minimum time, in seconds, to + // wait before attempting an explicit/implicit logout or an active task + // reassignment after an unexpected connection termination or a + // connection reset. + // A value of 0 indicates that logout or active task reassignment can be + // attempted immediately +#define ISCSI_LOGIN_AUTH_TEXT_KEY_DEFAULT_TIME_RETAIN "DefaultTime2Retain" // Use: LO + // Senders: Initiator and target + // Scope: SW + // DefaultTime2Retain=<numerical-value-0-to-3600> + // Default is 20. + // Result function is Minimum. + // The initiator and target negotiate the maximum time, in seconds, + // after an initial wait (Time2Wait), before which an active task + // reassignment is still possible after an unexpected connection + // termination or a connection reset. + // This value is also the session state timeout if the connection in + // question is the last LOGGED_IN connection in the session. + // A value of 0 indicates that connection/task state is immediately + // discarded by the target +#define ISCSI_LOGIN_AUTH_TEXT_KEY_MAX_OUTSTANDING_R2T "MaxOutstandingR2T" // Use: LO + // Senders: Initiator and target + // Scope: SW + // MaxOutstandingR2T=<numerical-value-from-1-to-65535> + // Irrelevant when: SessionType=Discovery + // Default is 1. + // Result function is Minimum. + // The initiator and target negotiate the maximum number of outstanding + // R2Ts per task, excluding any implied initial R2T that might be part + // of that task. An R2T is considered outstanding until the last data + // PDU (with the F bit set to 1) is transferred or a sequence reception + // timeout is encountered for that data sequence +#define ISCSI_LOGIN_AUTH_TEXT_KEY_DATA_PDU_IN_ORDER "DataPDUInOrder" // Use: LO + // Senders: Initiator and target + // Scope: SW + // Irrelevant when: SessionType=Discovery + // DataPDUInOrder=<boolean-value> + // Default is Yes. + // Result function is OR. + // "No" is used by iSCSI to indicate that the data PDUs within sequences + // can be in any order. "Yes" is used to indicate that data PDUs within + // sequences have to be at continuously increasing addresses and + // overlays are forbidden +#define ISCSI_LOGIN_AUTH_TEXT_KEY_DATA_SEQ_IN_ORDER "DataSequenceInOrder" // Use: LO + // Senders: Initiator and target + // Scope: SW + // Irrelevant when: SessionType=Discovery + // DataSequenceInOrder=<boolean-value> + // Default is Yes. + // Result function is OR. + // A data sequence is a sequence of Data-In or Data-Out PDUs that end + // with a Data-In or Data-Out PDU with the F bit set to 1. A Data-Out + // sequence is sent either unsolicited or in response to an R2T. + // Sequences cover an offset-range. + // If DataSequenceInOrder is set to No, data PDU sequences may be + // transferred in any order. + // If DataSequenceInOrder is set to Yes, data sequences MUST be + // transferred using continuously non-decreasing sequence offsets (R2T + // buffer offset for writes, or the smallest SCSI Data-In buffer offset + // within a read data sequence). + // If DataSequenceInOrder is set to Yes, a target may retry at most the + // last R2T, and an initiator may at most request retransmission for the + // last read data sequence. For this reason, if ErrorRecoveryLevel is + // not 0 and DataSequenceInOrder is set to Yes, then MaxOutstandingR2T + // MUST be set to 1 +#define ISCSI_LOGIN_AUTH_TEXT_KEY_ERR_RECOVERY_LEVEL "ErrorRecoveryLevel" // Use: LO + // Senders: Initiator and target + // Scope: SW + // ErrorRecoveryLevel=<numerical-value-0-to-2> + // Default is 0. + // Result function is Minimum. + // The initiator and target negotiate the recovery level supported. + // Recovery levels represent a combination of recovery capabilities. + // Each recovery level includes all the capabilities of the lower + // recovery levels and adds some new ones to them. + // In the description of recovery mechanisms, certain recovery classes + // are specified +#define ISCSI_LOGIN_AUTH_TEXT_KEY_PRIV_EXT_KEY_FMT "X-reversed.vendor" // Use: ALL + // Senders: Initiator and target + // Scope: specific key dependent + // X-reversed.vendor.dns_name.do_something= + // Keys with this format are used for private extension purposes. These + // keys always start with X- if unregistered with IANA (private). New + // public keys (if registered with IANA via an IETF Review RFC5226) no + // longer have an X# name prefix requirement; implementers may propose + // any intuitive unique name. + // For unregistered keys, to identify the vendor we suggest using the + // reversed DNS-name as a prefix to the key-proper. + // The part of key-name following X- MUST conform to the format for + // key-name. + // Vendor-specific keys MUST ONLY be used in Normal sessions. + // Support for public or private extension keys is OPTIONAL +#define ISCSI_LOGIN_AUTH_TEXT_KEY_TASK_REPORTING "TaskReporting" // Use: LO + // Senders: Initiator and target + // Scope: SW + // Irrelevant when: SessionType=Discovery + // TaskReporting=<list-of-values> + // Default is RFC3720. + // This key is used to negotiate the task completion reporting semantics + // from the SCSI target. The following table describes the semantics + // that an iSCSI target MUST support for respective negotiated key + // values. Whenever this key is negotiated, at least the RFC3720 and + // ResponseFence values MUST be offered as options by the negotiation + // originator. + // +--------------+------------------------------------------+ + // | Name | Description | + // +--------------+------------------------------------------+ + // | RFC3720 | RFC 3720-compliant semantics. Response | + // | | fencing is not guaranteed, and fast | + // | | completion of multi-task aborting is not | + // | | supported. | + // +--------------+------------------------------------------+ + // | ResponseFence| Response Fence | + // | | semantics MUST be supported in reporting | + // | | task completions. | + // +--------------+------------------------------------------+ + // | FastAbort | Updated fast multi-task abort semantics | + // | | defined in MUST be supported. Support | + // | | for the Response. Fence is implied - | + // | | i.e., semantics MUST be supported as | + // | | well. | + // +--------------+------------------------------------------+ + // When TaskReporting is not negotiated to FastAbort, the + // standard multi-task abort semantics MUST be used +#define ISCSI_LOGIN_AUTH_TEXT_KEY_X_NODE_ARCH "X#NodeArchitecture" // Use: LO, Declarative + // Senders: Initiator and target + // Scope: SW + // X#NodeArchitecture=<list-of-values> + // Default is None. + // Examples: + // X#NodeArchitecture=ExampleOS/v1234,ExampleInc_SW_Initiator/1.05a + // X#NodeArchitecture=ExampleInc_HW_Initiator/4010,Firmware/2.0.0.5 + // X#NodeArchitecture=ExampleInc_SW_Initiator/2.1,CPU_Arch/i686 + // This document does not define the structure or content of the list of + // values. + // The initiator or target declares the details of its iSCSI node + // architecture to the remote endpoint. These details may include, but + // are not limited to, iSCSI vendor software, firmware, or hardware + // versions; the OS version; or hardware architecture. This key may be + // declared on a Discovery session or a Normal session. + // The length of the key value (total length of the list-of-values) MUST + // NOT be greater than 255 bytes. + // X#NodeArchitecture MUST NOT be redeclared during the Login Phase. + // Functional behavior of the iSCSI node (this includes the iSCSI + // protocol logic - the SCSI, iSCSI, and TCP/IP protocols) MUST NOT + // depend on the presence, absence, or content of the X#NodeArchitecture + // key. The key MUST NOT be used by iSCSI nodes for interoperability or + // for exclusion of other nodes. To ensure proper use, key values + // SHOULD be set by the node itself, and there SHOULD NOT be provisions + // for the key values to contain user-defined text. + // Nodes implementing this key MUST choose one of the following + // implementation options: + // - only transmit the key, + // - only log the key values received from other nodes, or + // - both transmit and log the key values. + // Each node choosing to implement transmission of the key values MUST + // be prepared to handle the response of iSCSI nodes that do not + // understand the key. + // Nodes that implement transmission and/or logging of the key values + // may also implement administrative mechanisms that disable and/or + // change the logging and key transmission details. + // Thus, a valid behavior for this key may be that a node is completely + // silent (the node does not transmit any key value and simply discards + // any key values it receives without issuing a NotUnderstood response) +#define ISCSI_LOGIN_AUTH_TEXT_KEY_IF_MARKER "IFMarker" // Obsoleted Keys +#define ISCSI_LOGIN_AUTH_TEXT_KEY_OF_MARKER "OFMarker" // This document obsoletes the following keys defined in RFC3720: +#define ISCSI_LOGIN_AUTH_TEXT_KEY_OF_MARK_INT "OFMarkInt" // IFMarker, OFMarker, OFMarkInt, and IFMarkInt. However, iSCSI +#define ISCSI_LOGIN_AUTH_TEXT_KEY_IF_MARK_INT "IFMarkInt" // implementations compliant to this document may still receive these + // obsoleted keys - i.e., in a responder role - in a text negotiation. + // When an IFMarker or OFMarker key is received, a compliant iSCSI + // implementation SHOULD respond with the constant "Reject" value. The + // implementation MAY alternatively respond with a "No" value. + // However, the implementation MUST NOT respond with a "NotUnderstood" + // value for either of these keys. + // When an IFMarkInt or OFMarkInt key is received, a compliant iSCSI + // implementation MUST respond with the constant "Reject" value. The + // implementation MUST NOT respond with a "NotUnderstood" value for + // either of these keys + +/* This is an initiator-defined component of the session identifier and + is structured as follows: + + For the T field values 00b and 01b, a combination of A and B (for + 00b) or B and C (for 01b) identifies the vendor or organization whose + component (software or hardware) generates this ISID. A vendor or + organization with one or more OUIs, or one or more Enterprise + Numbers, MUST use at least one of these numbers and select the + appropriate value for the T field when its components generate ISIDs. + An OUI or EN MUST be set in the corresponding fields in network byte + order (byte big-endian). + + If the T field is 10b, B and C are set to a random 24-bit unsigned + integer value in network byte order (byte big-endian). + + The Qualifier field is a 16-bit or 24-bit unsigned integer value that + provides a range of possible values for the ISID within the selected + namespace. It may be set to any value within the constraints + specified in the iSCSI protocol. + + If the ISID is derived from something assigned to a hardware adapter + or interface by a vendor as a preset default value, it MUST be + configurable to a value assigned according to the SCSI port behavior + desired by the system in which it is installed. The resultant ISID + MUST also be persistent over power cycles, reboot, card swap, etc. +*/ +typedef struct __attribute__((packed)) iscsi_isid { + uint8_t a; // Meaning depends on T bit (see above) + uint16_t b; // Meaning depends on T bit (see above) + uint8_t c; // Meaning depends on T bit (see above) + uint16_t d; // Meaning depends on T bit (see above) +} iscsi_isid; + +#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_SECURITY_NEGOTIATION 0x0 // SecurityNegotiation +#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_LOGIN_OPERATIONAL_NEGOTIATION 0x1 // LoginOperationalNegotiation +#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE_FULL_FEATURE_PHASE 0x3 // FullFeaturePhase +#define ISCSI_LOGIN_REQ_FLAGS_NEXT_STAGE (1 << 0) // (NSG) - 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_REQS_FLAGS_GET_NEXT_STAGE(x) ((x) & 3) + +#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_SECURITY_NEGOTIATION 0x0 // SecurityNegotiation +#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_LOGIN_OPERATIONAL_NEGOTIATION 0x1 // LoginOperationalNegotiation +#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE_FULL_FEATURE_PHASE 0x3 // FullFeaturePhase +#define ISCSI_LOGIN_REQ_FLAGS_CURRENT_STAGE (1 << 2) // (CSG) - 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_REQS_FLAGS_GET_CURRENT_STAGE(x) (((x) >> 2) & 3) + +#define ISCSI_LOGIN_REQ_FLAGS_CONTINUE (1 << 6) // (C) When set to 1, this bit indicates that the text (set of key=value + // pairs) in this Login Request is not complete (it will be continued on + // subsequent Login Requests); otherwise, it indicates that this Login + // Request ends a set of key=value pairs. A Login Request with the + // C bit set to 1 MUST have the T bit set to 0. +#define ISCSI_LOGIN_REQ_FLAGS_TRANSMIT (1 << 7) // (T) When set to 1, this bit indicates that the initiator is ready to + // transit to the next stage. + // If the T bit is set to 1 and the NSG is set to FullFeaturePhase, then + // this also indicates that the initiator is ready for the Login + // Final-Response. + +/* After establishing a TCP connection between an initiator and a + target, the initiator MUST start a Login Phase to gain further access + to the target's resources. + + The Login Phase consists of a sequence of Login Requests and Login + Responses that carry the same Initiator Task Tag. + + Login Requests are always considered as immediate. +*/ +typedef struct __attribute__((packed)) iscsi_login_req_packet { + uint8_t opcode; // Always 0x03 according to specification (see above) + int8_t flags; // Login request flags (see above) + uint8_t version_max; // Version-max indicates the maximum version number supported. + // All Login Requests within the Login Phase MUST carry the same + // Version-max. Currently, this is always 0 + // The target MUST use the value presented with the first Login Request. + uint8_t version_min; // All Login Requests within the Login Phase MUST carry the same + // Version-min. The target MUST use the value presented with the first + // Login Request. Always 0 for now + uint8_t total_ahs_len; // TotalAHSLength + uint8_t ds_len[3]; // DataSegmentLength + struct iscsi_isid isid; // ISID (see above for declaration) + uint16_t tsih; // The TSIH must be set in the first Login Request. The reserved value + // 0 MUST be used on the first connection for a new session. Otherwise, + // the TSIH sent by the target at the conclusion of the successful login + // of the first connection for this session MUST be used. The TSIH + // identifies to the target the associated existing session for this new + // connection. + // All Login Requests within a Login Phase MUST carry the same TSIH. + // The target MUST check the value presented with the first Login + // Request + uint32_t init_task_tag; // Initiator task tag + uint16_t cid; // Connection ID. The CID provides a unique ID for this connection within the session. + // All Login Requests within the Login Phase MUST carry the same CID. + // The target MUST use the value presented with the first Login Request. + // A Login Request with a non-zero TSIH and a CID equal to that of an + // existing connection implies a logout of the connection followed by a + // login + uint16_t reserved; // Reserved for future usage + uint32_t cmd_sn; // The CmdSN is either the initial command sequence number of a session + // (for the first Login Request of a session - the "leading" login) or + // the command sequence number in the command stream if the login is for + // a new connection in an existing session. + // Examples: + // - Login on a leading connection: If the leading login carries the + // CmdSN 123, all other Login Requests in the same Login Phase carry + // the CmdSN 123, and the first non-immediate command in the Full + // Feature Phase also carries the CmdSN 123. + // - Login on other than a leading connection: If the current CmdSN at + // the time the first login on the connection is issued is 500, then + // that PDU carries CmdSN=500. Subsequent Login Requests that are + // needed to complete this Login Phase may carry a CmdSN higher than + // 500 if non-immediate requests that were issued on other connections + // in the same session advance the CmdSN. + // If the Login Request is a leading Login Request, the target MUST use + // the value presented in the CmdSN as the target value for the + // ExpCmdSN + uint32_t exp_stat_sn; // For the first Login Request on a connection, this is the ExpStatSN + // for the old connection, and this field is only valid if the Login + // Request restarts a connection + // For subsequent Login Requests, it is used to acknowledge the Login + // Responses with their increasing StatSN values + uint64_t reserved2[2]; // Reserved for future usage + struct iscsi_ds_cmd_data ds_cmd_data; // Data segment - Login Parameters in Text Request Format + // The initiator MUST provide some basic parameters in order + // to enable the target to determine if the initiator may use + // the target's resources and the initial text parameters for the security exchange +} iscsi_login_req_packet; + +#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_SECURITY_NEGOTIATION 0x0 // SecurityNegotiation +#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_LOGIN_OPERATIONAL_NEGOTIATION 0x1 // LoginOperationalNegotiation +#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE_FULL_FEATURE_PHASE 0x3 // FullFeaturePhase +#define ISCSI_LOGIN_RESPONSE_FLAGS_NEXT_STAGE (1 << 0) // (NSG) - 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_GET_NEXT_STAGE(x) ((x) & 3) + +#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_SECURITY_NEGOTIATION 0x0 // SecurityNegotiation +#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_LOGIN_OPERATIONAL_NEGOTIATION 0x1 // LoginOperationalNegotiation +#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE_FULL_FEATURE_PHASE 0x3 // FullFeaturePhase +#define ISCSI_LOGIN_RESPONSE_FLAGS_CURRENT_STAGE (1 << 2) // (CSG) - 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_GET_CURRENT_STAGE(x) (((x) >> 2) & 3) + +#define ISCSI_LOGIN_RESPONSE_FLAGS_CONTINUE (1 << 6) // (C) When set to 1, this bit indicates that the text (set of key=value + // pairs) in this Login Response is not complete (it will be continued + // on subsequent Login Responses); otherwise, it indicates that this + // Login Response ends a set of key=value pairs. A Login Response with + // the C bit set to 1 MUST have the T bit set to 0 +#define ISCSI_LOGIN_RESPONSE_FLAGS_TRANSMIT (1 << 7) // (T) The T bit is set to 1 as an indicator of the end of the stage. If + // the T bit is set to 1 and the NSG is set to FullFeaturePhase, then + // this is also the Login Final-Response. A T bit of 0 indicates a + // "partial" response, which means "more negotiation needed". + // A Login Response with the T bit set to 1 MUST NOT contain key=value + // pairs that may require additional answers from the initiator within + // the same stage. + // If the Status-Class is 0, the T bit MUST NOT be set to 1 if the T bit + // in the request was set to 0 + +#define ISCSI_LOGIN_RESPONSE_STATUS_CLASS_SUCCESS 0x00 // Success - indicates that the iSCSI target successfully + // received, understood, and accepted the request. The numbering + // fields (StatSN, ExpCmdSN, MaxCmdSN) are only valid if Status- + // Class is 0 +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SUCCESS 0x00 // Login is proceeding OK. If the response T bit is set to 1 in both the + // request and the matching response, and the NSG is set to + // FullFeaturePhase in both the request and the matching response, the + // Login Phase is finished, and the initiator may proceed to issue SCSI + // commands. + +#define ISCSI_LOGIN_RESPONSE_STATUS_CLASS_REDIRECT 0x01 // Redirection - indicates that the initiator must take further + // action to complete the request. This is usually due to the + // target moving to a different address. All of the redirection + // Status-Class responses MUST return one or more text key + // parameters of the type "TargetAddress", which indicates the + // target's new address. A redirection response MAY be issued by + // a target prior to or after completing a security negotiation if + // a security negotiation is required. A redirection SHOULD be + // accepted by an initiator, even without having the target + // complete a security negotiation if any security negotiation is + // required, and MUST be accepted by the initiator after the + // completion of the security negotiation if any security + // negotiation is required +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_REDIRECT_TEMP 0x01 // The requested iSCSI Target Name (ITN) has temporarily moved + // to the address provided +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_REDIRECT_PERM 0x02 // The requested ITN has permanently moved to the address provided + +#define ISCSI_LOGIN_RESPONSE_STATUS_CLASS_CLIENT_ERR 0x02 // Initiator Error (not a format error) - indicates that the + // initiator most likely caused the error. This MAY be due to a + // request for a resource for which the initiator does not have + // permission. The request should not be tried again +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISC 0x00 // Miscellaneous iSCSI initiator errors +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_AUTH_ERR 0x01 // The initiator could not be successfully + // authenticated or target authentication is + // not supported +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_AUTH_FAIL 0x02 // The initiator is not allowed access to the + // given target +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_NOT_FOUND 0x03 // The requested ITN does not exist at this + // address +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_TARGET_REMOVED 0x04 // The requested ITN has been removed, and + // no forwarding address is provided +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_WRONG_VERSION 0x05 // The requested iSCSI version range is not + // supported by the target +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_TOO_MANY_CONNECTIONS 0x06 // Too many connections on this SSID +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_MISSING_PARAMETER 0x07 // Missing parameters (e.g. iSCSI Initiator + // Name and/or Target Name) +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_NO_SESSION_SPANNING 0x08 // arget does not support session spanning + // to this connection (address) +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_SESSION_NO_SUPPORT 0x09 // Target does not support this type of + // session or not from this initiator +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_SESSION_NO_EXIST 0x0A // Attempt to add a connection to a non- + // existent session +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_CLIENT_ERR_INVALID_LOGIN_REQ_TYPE 0x0B // Invalid request type during login + +#define ISCSI_LOGIN_RESPONSE_STATUS_CLASS_SERVER_ERR 0x03 // Target Error - indicates that the target sees no errors in the + // initiator's Login Request but is currently incapable of + // fulfilling the request. The initiator may retry the same Login + // Request later +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SERVER_ERR_TARGET_ERROR 0x00 // Target hardware or software error +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SERVER_ERR_SERVICE_UNAVAILABLE 0x01 // The iSCSI service or target is not + // currently operational +#define ISCSI_LOGIN_RESPONSE_STATUS_DETAILS_SERVER_ERR_OUT_OF_RESOURCES 0x02 // The target has insufficient session, + // connection, or other resources + +/* The Login Response indicates the progress and/or end of the Login + Phase. +*/ +typedef struct __attribute__((packed)) iscsi_login_response_packet { + uint8_t opcode; // Always 0x23 according to specification (see above) + int8_t flags; // Login response flags (see above) + uint8_t version_max; // This is the highest version number supported by the target. + // All Login Responses within the Login Phase MUST carry the same + // Version-max + uint8_t version_active; // Version-active indicates the highest version supported by the target + // and initiator. If the target does not support a version within the + // range specified by the initiator, the target rejects the login and + // this field indicates the lowest version supported by the target. + // All Login Responses within the Login Phase MUST carry the same + // Version-active. + // The initiator MUST use the value presented as a response to the first + // Login Request + uint8_t total_ahs_len; // TotalAHSLength + uint8_t ds_len[3]; // DataSegmentLength + struct iscsi_isid isid; // ISID (see above for declaration) + uint16_t tsih; // The TSIH is the target-assigned session-identifying handle. Its + // internal format and content are not defined by this protocol, except + // for the value 0, which is reserved. With the exception of the Login + // Final-Response in a new session, this field should be set to the TSIH + // provided by the initiator in the Login Request. For a new session, + // the target MUST generate a non-zero TSIH and ONLY return it in the + // Login Final-Response + uint32_t init_task_tag; // Initiator task tag + uint32_t reserved; // Reserved for future usage + uint32_t stat_sn; // For the first Login Response (the response to the first Login + // Request), this is the starting status sequence number for the + // connection. The next response of any kind - including the next + // Login Response, if any, in the same Login Phase - will carry this + // number + 1. This field is only valid if the Status-Class is 0 + uint32_t exp_cmd_sn; // ExpCmdSN + uint32_t max_cmd_sn; // MaxCmdSN + uint8_t status_class; // Status-class (see above for details). If the Status-Class is + // not 0, the initiator and target MUST close the TCP connection + // If the target wishes to reject the Login Request for more than one + // reason, it should return the primary reason for the rejection + uint8_t status_detail; // Status-detail (see above for details) + uint16_t reserved2; // Reserved for future usage + uint64_t reserved3; // Reserved for future usage + struct iscsi_ds_cmd_data ds_cmd_data; // Data segment - Login Parameters in Text Request Format + // The target MUST provide some basic parameters in order to enable the + // initiator to determine if it is connected to the correct port and the + // initial text parameters for the security exchange. + // All the rules specified for Text Responses also hold for Login + // Responses +} iscsi_login_response_packet; + +#define ISCSI_LOGOUT_REQ_REASON_CODE_CLOSE_SESSION 0x00 // Close the session. All commands associated with the + // session (if any) are terminated +#define ISCSI_LOGOUT_REQ_REASON_CODE_CLOSE_CONNECTION 0x01 // Close the connection. All commands associated with the + // connection (if any) are terminated +#define ISCSI_LOGOUT_REQ_REASON_CODE_REMOVE_CONNECTION_RECOVERY 0x02 // Remove the connection for recovery. The connection is + // closed, and all commands associated with it, if any, are + // to be prepared for a new allegiance + +/* The entire logout discussion in this section is also applicable for + an implicit Logout realized by way of a connection reinstatement or + session reinstatement. When a Login Request performs an implicit + Logout, the implicit Logout is performed as if having the reason + codes specified below: +*/ +#define ISCSI_LOGOUT_REQ_REASON_CODE_IMPLICIT_SESSION_REINSTATEMENT 0x00 // Session reinstatement +#define ISCSI_LOGOUT_REQ_REASON_CODE_IMPLICIT_CONNECTION_REINSTATEMENT 0x01 // connection reinstatement when the operational + // ErrorRecoveryLevel < 2 +#define ISCSI_LOGOUT_REQ_REASON_CODE_IMPLICIT_CONNECTION_REINSTATEMENT_2 0x02 // connection reinstatement when the operational + // ErrorRecoveryLevel = 2 + +/* The Logout Request is used to perform a controlled closing of a + connection. + + An initiator MAY use a Logout Request to remove a connection from a + session or to close an entire session. + + After sending the Logout Request PDU, an initiator MUST NOT send any + new iSCSI requests on the closing connection. If the Logout Request + is intended to close the session, new iSCSI requests MUST NOT be sent + on any of the connections participating in the session. + + When receiving a Logout Request with the reason code "close the + connection" or "close the session", the target MUST terminate all + pending commands, whether acknowledged via the ExpCmdSN or not, on + that connection or session, respectively. + + When receiving a Logout Request with the reason code "remove the + connection for recovery", the target MUST discard all requests not + yet acknowledged via the ExpCmdSN that were issued on the specified + connection and suspend all data/status/R2T transfers on behalf of + pending commands on the specified connection. + + The target then issues the Logout Response and half-closes the TCP + connection (sends FIN). After receiving the Logout Response and + attempting to receive the FIN (if still possible), the initiator MUST + completely close the logging-out connection. For the terminated + commands, no additional responses should be expected. + + A Logout for a CID may be performed on a different transport + connection when the TCP connection for the CID has already been + terminated. In such a case, only a logical "closing" of the iSCSI + connection for the CID is implied with a Logout. + + All commands that were not terminated or not completed (with status) + and acknowledged when the connection is closed completely can be + reassigned to a new connection if the target supports connection + recovery. + + If an initiator intends to start recovery for a failing connection, + it MUST use the Logout Request to "clean up" the target end of a + failing connection and enable recovery to start, or use the Login + Request with a non-zero TSIH and the same CID on a new connection for + the same effect. In sessions with a single connection, the + connection can be closed and then a new connection reopened. A + connection reinstatement login can be used for recovery. + + A successful completion of a Logout Request with the reason code + "close the connection" or "remove the connection for recovery" + results at the target in the discarding of unacknowledged commands + received on the connection being logged out. These are commands that + have arrived on the connection being logged out but that have not + been delivered to SCSI because one or more commands with a smaller + CmdSN have not been received by iSCSI. The resulting holes in the + command sequence numbers will have to be handled by appropriate + recovery, unless the session is also closed. +*/ +typedef struct __attribute__((packed)) iscsi_logout_req_packet { + uint8_t opcode; // Always 0x06 according to specification (see above) + int8_t reason_code; // Reason Code + // A target implicitly terminates the active tasks due to the iSCSI + // protocol in the following cases: + // a) When a connection is implicitly or explicitly logged out with + // the reason code "close the connection" and there are active + // tasks allegiant to that connection. + // b) When a connection fails and eventually the connection state + // times out and there are active tasks allegiant to that + // connection + // c) When a successful recovery Logout is performed while there are + // active tasks allegiant to that connection and those tasks + // eventually time out after the Time2Wait and Time2Retain periods + // without allegiance reassignment + // d) When a connection is implicitly or explicitly logged out with + // the reason code "close the session" and there are active tasks + // in that session + // If the tasks terminated in any of the above cases are SCSI tasks, + // they must be internally terminated as if with CHECK CONDITION status. + // This status is only meaningful for appropriately handling the + // internal SCSI state and SCSI side effects with respect to ordering, + // because this status is never communicated back as a terminating + // status to the initiator. However, additional actions may have to be + // taken at the SCSI level, depending on the SCSI context as defined by + // the SCSI standards (e.g., queued commands and ACA; UA for the next + // command on the I_T nexus in cases a), b), and c) above). After the + // tasks are terminated, the target MUST report a Unit Attention condition + // on the next command processed on any connection for each affected + // I_T_L nexus with the status of CHECK CONDITION, the ASC/ASCQ value + // of 0x47 / 0x7F ("SOME COMMANDS CLEARED BY ISCSI PROTOCOL EVENT"), etc. + uint16_t reserved; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength (MUST be 0 for this PDU) + uint8_t ds_len[3]; // DataSegmentLength (MUST be 0 for this PDU) + uint64_t reserved2; // Reserved for future usage + uint32_t init_task_tag; // Initiator task tag + uint16_t cid; // This is the connection ID of the connection to be closed (including + // closing the TCP stream). This field is only valid if the reason code + // is not "close the session" + uint16_t reserved3; // Reserved for future usage + uint32_t cmd_sn; // CmdSN + uint32_t exp_stat_sn; // This is the last ExpStatSN value for the connection to be closed + uint64_t reserved4[2]; // Reserved for future usage + struct iscsi_header_digest hdr_digest; // Optional header digest +} iscsi_logout_req_packet; + +#define ISCSI_LOGOUT_RESPONSE_CLOSED_SUCCESSFULLY 0x00 // Connection or session closed successfully +#define ISCSI_LOGOUT_RESPONSE_CID_NOT_FOUND 0x01 // CID not found +#define ISCSI_LOGOUT_RESPONSE_CONNECTION_RECOVERY_NOT_SUPPORTED 0x02 // Connection recovery is not supported (i.e., the Logout reason + // code was "remove the connection for recovery" and the target + // does not support it as indicated by the operational + // ErrorRecoveryLevel) +#define ISCSI_LOGOUT_RESPONSE_CLEANUP_FAILED 0x03 // Cleanup failed for various reasons + +/* The Logout Response is used by the target to indicate if the cleanup + operation for the connection(s) has completed. + + After Logout, the TCP connection referred by the CID MUST be closed + at both ends (or all connections must be closed if the logout reason + was session close). +*/ +typedef struct __attribute__((packed)) iscsi_logout_response_packet { + uint8_t opcode; // Always 0x26 according to specification (see above) + uint8_t flags; // Reserved for future usage + uint8_t response; // Response + uint8_t reserved; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength (MUST be 0 for this PDU) + uint8_t ds_len[3]; // DataSegmentLength (MUST be 0 for this PDU) + uint64_t reserved2; // Reserved for future usage + uint32_t init_task_tag; // Initiator task tag + uint32_t reserved3; // Reserved for future usage + uint32_t stat_sn; // StatSN + uint32_t exp_cmd_sn; // ExpCmdSN + uint32_t max_cmd_sn; // MaxCmdSN + uint32_t reserved4; // Reserved for future usage + uint16_t time_wait; // Time2Wait + // If the Logout response code is 0 and the operational + // ErrorRecoveryLevel is 2, this is the minimum amount of time, in + // seconds, to wait before attempting task reassignment. If the Logout + // response code is 0 and the operational ErrorRecoveryLevel is less + // than 2, this field is to be ignored. + // This field is invalid if the Logout response code is 1. + // If the Logout response code is 2 or 3, this field specifies the + // minimum time to wait before attempting a new implicit or explicit + // logout. + // If Time2Wait is 0, the reassignment or a new Logout may be attempted + // immediately + uint16_t time_retain; // Time2Retain + // If the Logout response code is 0 and the operational + // ErrorRecoveryLevel is 2, this is the maximum amount of time, in + // seconds, after the initial wait (Time2Wait) that the target waits for + // the allegiance reassignment for any active task, after which the task + // state is discarded. If the Logout response code is 0 and the + // operational ErrorRecoveryLevel is less than 2, this field is to be + // ignored. + // This field is invalid if the Logout response code is 1. + // If the Logout response code is 2 or 3, this field specifies the + // maximum amount of time, in seconds, after the initial wait + // (Time2Wait) that the target waits for a new implicit or explicit + // logout. + // If it is the last connection of a session, the whole session state is + // discarded after Time2Retain. + // If Time2Retain is 0, the target has already discarded the connection + // (and possibly the session) state along with the task states. No + // reassignment or Logout is required in this case + uint32_t reserved5; // Reserved for future usage + struct iscsi_header_digest hdr_digest; // Optional header digest +} iscsi_logout_response_packet; + +#define ISCSI_SNACK_REQ_TYPE_DATA_R2T_SNACK 0x00 // Data/R2T SNACK: requesting retransmission of one or more + // Data-In or R2T PDUs +#define ISCSI_SNACK_REQ_TYPE_STATUS_SNACK 0x01 // Status SNACK: requesting retransmission of one or more + // numbered responses +#define ISCSI_SNACK_REQ_TYPE_DATA_ACK 0x02 // DataACK: positively acknowledges Data-In PDUs. + // If an initiator operates at ErrorRecoveryLevel 1 or higher, it MUST + // issue a SNACK of type DataACK after receiving a Data-In PDU with the + // A bit set to 1. However, if the initiator has detected holes in the + // input sequence, it MUST postpone issuing the SNACK of type DataACK + // until the holes are filled. An initiator MAY ignore the A bit if it + // deems that the bit is being set aggressively by the target (i.e., + // before the MaxBurstLength limit is reached). + // The DataACK is used to free resources at the target and not to + // request or imply data retransmission. + // An initiator MUST NOT request retransmission for any data it had + // already acknowledged +#define ISCSI_SNACK_REQ_TYPE_R_DATA_SNACK 0x03 // R-Data SNACK: requesting retransmission of Data-In PDUs with + // possible resegmentation and status tagging. + // If the initiator MaxRecvDataSegmentLength changed between the + // original transmission and the time the initiator requests + // retransmission, the initiator MUST issue a R-Data SNACK. + // With R-Data SNACK, the initiator indicates that it discards all the + // unacknowledged data and expects the target to resend it. It also + // expects resegmentation. In this case, the retransmitted Data-In PDUs + // MAY be different from the ones originally sent in order to reflect + // changes in MaxRecvDataSegmentLength. Their DataSN starts with the + // BegRun of the last DataACK received by the target if any was received; + // otherwise, it starts with 0 and is increased by 1 for each resent + // Data-In PDU. + // A target that has received a R-Data SNACK MUST return a SCSI Response + // that contains a copy of the SNACK Tag field from the R-Data SNACK in + // the SCSI Response SNACK Tag field as its last or only Response. For + // example, if it has already sent a response containing another value + // in the SNACK Tag field or had the status included in the last Data-In + // PDU, it must send a new SCSI Response PDU. If a target sends more + // than one SCSI Response PDU due to this rule, all SCSI Response PDUs + // must carry the same StatSN. If an initiator attempts to recover a lost + // SCSI Response when more than one response has been sent, the + // target will send the SCSI Response with the latest content known to + // the target, including the last SNACK Tag for the command. + // For considerations in allegiance reassignment of a task to a + // connection with a different MaxRecvDataSegmentLength. + +/* If the implementation supports ErrorRecoveryLevel greater than zero, + it MUST support all SNACK types. + + The SNACK is used by the initiator to request the retransmission of + numbered responses, data, or R2T PDUs from the target. The SNACK + Request indicates the numbered responses or data "runs" whose + retransmission is requested, where the run starts with the first + StatSN, DataSN, or R2TSN whose retransmission is requested and + indicates the number of Status, Data, or R2T PDUs requested, + including the first. 0 has special meaning when used as a starting + number and length: + + - When used in RunLength, it means all PDUs starting with the + initial. + + - When used in both BegRun and RunLength, it means all + unacknowledged PDUs. + + The numbered response(s) or R2T(s) requested by a SNACK MUST be + delivered as exact replicas of the ones that the target transmitted + originally, except for the fields ExpCmdSN, MaxCmdSN, and ExpDataSN, + which MUST carry the current values. R2T(s)requested by SNACK MUST + also carry the current value of the StatSN. + + The numbered Data-In PDUs requested by a Data SNACK MUST be delivered + as exact replicas of the ones that the target transmitted originally, + except for the fields ExpCmdSN and MaxCmdSN, which MUST carry the + current values; and except for resegmentation. + + Any SNACK that requests a numbered response, data, or R2T that was + not sent by the target or was already acknowledged by the initiator + MUST be rejected with a reason code of "Protocol Error". +*/ +typedef struct __attribute__((packed)) iscsi_snack_req_packet { + uint8_t opcode; // Always 0x10 according to specification (see above) + int8_t type; // Type + // Data/R2T SNACK, Status SNACK, or R-Data SNACK for a command MUST + // precede status acknowledgment for the given command + uint16_t reserved; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength + uint8_t ds_len[3]; // DataSegmentLength + uint64_t lun; // LUN or Reserved + uint32_t init_task_tag; // For a Status SNACK and DataACK, the Initiator Task Tag MUST be set to + // the reserved value 0xFFFFFFFF. In all other cases, the Initiator + // Task Tag field MUST be set to the Initiator Task Tag of the + // referenced command + uint32_t target_xfer_snack_tag; // For a R-Data SNACK, this field MUST contain a value that is different + // from 0 or 0xFFFFFFFF and is unique for the task (identified by the + // Initiator Task Tag). This value MUST be copied by the iSCSI target + // in the last or only SCSI Response PDU it issues for the command. + // For DataACK, the Target Transfer Tag MUST contain a copy of the + // Target Transfer Tag and LUN provided with the SCSI Data-In PDU with + // the A bit set to 1. + // In all other cases, the Target Transfer Tag field MUST be set to the + // reserved value 0xFFFFFFFF + uint32_t reserved2; // Reserved for future usage + uint32_t exp_stat_sn; // ExpStatSN + uint32_t reserved3; // Reserved for future usage + uint32_t beg_run; // BegRun + // This field indicates the DataSN, R2TSN, or StatSN of the first PDU + // whose retransmission is requested (Data/R2T and Status SNACK), or the + // next expected DataSN (DataACK SNACK). + // A BegRun of 0, when used in conjunction with a RunLength of 0, means + // "resend all unacknowledged Data-In, R2T or Response PDUs". + // BegRun MUST be 0 for a R-Data SNACK + uint32_t run_len; // RunLength + // This field indicates the number of PDUs whose retransmission is + // requested. + // A RunLength of 0 signals that all Data-In, R2T, or Response PDUs + // carrying the numbers equal to or greater than BegRun have to be + // resent. + // The RunLength MUST also be 0 for a DataACK SNACK in addition to a + // R-Data SNACK + struct iscsi_header_digest hdr_digest; // Optional header digest +} iscsi_snack_req_packet; + +#define ISCSI_REJECT_REASON_RESERVED 0x01 // Reserved, original PDU can't be resent +#define ISCSI_REJECT_REASON_DATA_DIGEST_ERR 0x02 // Data (payload) digest error, original + // PDU can be resent. + // For iSCSI, Data-Out PDU retransmission is only done if the + // target requests retransmission with a recovery R2T. However, + // if this is the data digest error on immediate data, the + // initiator may choose to retransmit the whole PDU, including + // the immediate data +#define ISCSI_REJECT_REASON_SNACK_REJECT 0x03 // SNACK Reject (original PDU can be resent) +#define ISCSI_REJECT_REASON_PROTOCOL_ERR 0x04 // Protocol Error (e.g., SNACK Request for a status that was + // already acknowledged). Original PDU can't be resent' +#define ISCSI_REJECT_REASON_COMMAND_NOT_SUPPORTED 0x05 // Command not supported (original PDU can't be resent) +#define ISCSI_REJECT_REASON_TOO_MANY_IMMEDIATE_COMMANDS 0x06 // Immediate command reject - too many immediate + // commands (original PDU can be resent) +#define ISCSI_REJECT_REASON_TASK_IN_PROGRESS 0x07 // Task in progress (original PDU can't be resent) +#define ISCSI_REJECT_REASON_INVALID_DATA_ACK 0x08 // Invalid data ack (original PDU can't be resent) +#define ISCSI_REJECT_REASON_INVALID_PDU_FIELD 0x09 // Invalid PDU field, original PDU can't be resent. + // A target should use this reason code for all invalid values + // of PDU fields that are meant to describe a task, a response, + // or a data transfer. Some examples are invalid TTT/ITT, + // buffer offset, LUN qualifying a TTT, and an invalid sequence + // number in a SNACK +#define ISCSI_REJECT_REASON_OUT_OF_RESOURCES 0x0A // Long op reject - Can't generate Target Transfer Tag - out of + // resources. Original PDU can be resent later +#define ISCSI_REJECT_REASON_DEPRECATED 0x0B // Deprecated; MUST NOT be used. Reason code 0x0B is deprecated + // and MUST NOT be used by implementations. An implementation + // receiving reason code 0x0B MUST treat it as a negotiation + // failure that terminates the Login Phase and the TCP connection +#define ISCSI_REJECT_REASON_WAITING_FOR_LOGOUT 0x0C // Waiting for Logout, original PDU can't be resent + +typedef struct __attribute__((packed)) iscsi_reject_packet { + uint8_t opcode; // Always 0x3F according to specification (see above) + uint8_t flags; // Reserved for future usage + uint8_t reason; // Reject reason (see above for definitions). + // In all the cases in which a pre-instantiated SCSI task is terminated + // because of the reject, the target MUST issue a proper SCSI command + // response with CHECK CONDITION. In these cases in which a status for + // the SCSI task was already sent before the reject, no additional + // status is required. If the error is detected while data from the + // initiator is still expected (i.e., the command PDU did not contain + // all the data and the target has not received a Data-Out PDU with the + // Final bit set to 1 for the unsolicited data, if any, and all + // outstanding R2Ts, if any), the target MUST wait until it receives + // the last expected Data-Out PDUs with the F bit set to 1 before + // sending the Response PDU + uint8_t reserved; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength + uint8_t ds_len[3]; // DataSegmentLength + uint64_t reserved2; // Reserved for future usage + uint32_t tag; // Always 0xFFFFFFFF for now + uint32_t reserved3; // Reserved for future usage + uint32_t stat_sn; // StatSN. This field carries its usual value and is not related to the + // rejected command. The StatSN is advanced after a Reject + uint32_t exp_cmd_sn; // ExpCmdSN. This field carries its usual value and is not related to the + // rejected command + uint32_t max_cmd_sn; // MaxCmdSN. This field carries its usual value and is not related to the + // rejected command + uint32_t data_r2tsn_sn; // DataSN/R2TSN or Reserved. + // This field is only valid if the rejected PDU is a Data/R2T SNACK and + // the Reject reason code is "Protocol Error". The DataSN/R2TSN is the + // next Data/R2T sequence number that the target would send for the + // task, if any + uint32_t reserved4[2]; // Reserved for future usage + struct iscsi_header_digest hdr_digest; // Optional header digest + struct iscsi_bhs_packet bad_pdu_hdr; // Complete Header of Bad PDU. The target returns the + // header (not including the digest) of the PDU in error + // as the data of the response + uint8_t vendor_data[0]; // Vendor-specific data (if any) + struct iscsi_data_digest data_digest; // Optional data digest +} iscsi_reject_packet; + +/* NOP-Out may be used by an initiator as a "ping request" to verify + that a connection/session is still active and all its components are + operational. The NOP-In response is the "ping echo". + + A NOP-Out is also sent by an initiator in response to a NOP-In. + + A NOP-Out may also be used to confirm a changed ExpStatSN if another + PDU will not be available for a long time. + + Upon receipt of a NOP-In with the Target Transfer Tag set to a valid + value (not the reserved value 0xffffffff), the initiator MUST respond + with a NOP-Out. In this case, the NOP-Out Target Transfer Tag MUST + contain a copy of the NOP-In Target Transfer Tag. The initiator + + SHOULD NOT send a NOP-Out in response to any other received NOP-In, + in order to avoid lengthy sequences of NOP-In and NOP-Out PDUs sent + in response to each other. +*/ +typedef struct __attribute__((packed)) iscsi_nop_out_packet { + uint8_t opcode; // Always 0x00 according to specification (see above) + uint8_t reserved[3]; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength + uint8_t ds_len[3]; // DataSegmentLength + uint64_t lun; // LUN or Reserved + uint32_t init_task_tag; // The NOP-Out MUST have the Initiator Task Tag set to a valid value + // only if a response in the form of a NOP-In is requested (i.e., the + // NOP-Out is used as a ping request). Otherwise, the Initiator Task + // Tag MUST be set to 0xFFFFFFFF. + // When a target receives the NOP-Out with a valid Initiator Task Tag, + // it MUST respond with a NOP-In Response. + // If the Initiator Task Tag contains 0xFFFFFFFF, the I bit MUST be set + // to 1, and the CmdSN is not advanced after this PDU is sent + uint32_t target_xfer_tag; // The Target Transfer Tag is a target-assigned identifier for the + // operation. + // The NOP-Out MUST only have the Target Transfer Tag set if it is + // issued in response to a NOP-In with a valid Target Transfer Tag. In + // this case, it copies the Target Transfer Tag from the NOP-In PDU. + // Otherwise, the Target Transfer Tag MUST be set to 0xFFFFFFFF. + // When the Target Transfer Tag is set to a value other than 0xFFFFFFFF, + // the LUN field MUST also be copied from the NOP-In + uint32_t cmd_sn; // CmdSN + uint32_t exp_stat_sn; // ExpStatSN + uint64_t reserved2[2]; // Reserved for future usage + struct iscsi_header_digest hdr_digest; // Optional header digest + struct iscsi_ds_cmd_data ds_ping_data; // DataSegment - Ping Data (optional) + // Ping data is reflected in the NOP-In Response. The length of the + // reflected data is limited to MaxRecvDataSegmentLength. The length of + // ping data is indicated by the DataSegmentLength. 0 is a valid value + // for the DataSegmentLength and indicates the absence of ping data + struct iscsi_data_digest data_digest; // Optional data digest +} iscsi_nop_out_packet; + +/* NOP-In is sent by a target as either a response to a NOP-Out, a + "ping" to an initiator, or a means to carry a changed ExpCmdSN and/or + MaxCmdSN if another PDU will not be available for a long time (as + determined by the target). + + When a target receives the NOP-Out with a valid Initiator Task Tag + (not the reserved value 0xFFFFFFFF), it MUST respond with a NOP-In + with the same Initiator Task Tag that was provided in the NOP-Out + request. It MUST also duplicate up to the first + MaxRecvDataSegmentLength bytes of the initiator-provided Ping Data. + For such a response, the Target Transfer Tag MUST be 0xFFFFFFFF. The + + target SHOULD NOT send a NOP-In in response to any other received + NOP-Out in order to avoid lengthy sequences of NOP-In and NOP-Out + PDUs sent in response to each other. + + Otherwise, when a target sends a NOP-In that is not a response to a + NOP-Out received from the initiator, the Initiator Task Tag MUST be + set to 0xFFFFFFFF, and the data segment MUST NOT contain any data + (DataSegmentLength MUST be 0). +*/ + +typedef struct __attribute__((packed)) iscsi_nop_in_packet { + uint8_t opcode; // Always 0x20 according to specification (see above) + uint8_t reserved[3]; // Reserved for future usage + uint8_t total_ahs_len; // TotalAHSLength + uint8_t ds_len[3]; // DataSegmentLength + uint64_t lun; // A LUN MUST be set to a correct value when the Target Transfer Tag is + // valid (not the reserved value 0xFFFFFFFF) + uint32_t init_task_tag; // Initiator task tag or 0xFFFFFFFF + uint32_t target_xfer_tag; // If the target is responding to a NOP-Out, this field is set to the + // reserved value 0xFFFFFFFF. + // If the target is sending a NOP-In as a ping (intending to receive a + // corresponding NOP-Out), this field is set to a valid value (not the + // reserved value 0xFFFFFFFF). + // If the target is initiating a NOP-In without wanting to receive a + // corresponding NOP-Out, this field MUST hold the reserved value + // 0xFFFFFFFF + uint32_t stat_sn; // The StatSN field will always contain the next StatSN. However, when + // the Initiator Task Tag is set to 0xFFFFFFFF, the StatSN for the + // connection is not advanced after this PDU is sent + uint32_t exp_cmd_sn; // ExpCmdSN + uint32_t max_cmd_sn; // MaxCmdSN + uint32_t reserved2[3]; // Reserved for future usage + struct iscsi_header_digest hdr_digest; // Optional header digest + struct iscsi_ds_cmd_data ds_ping_data; // DataSegment - Return Ping Data + struct iscsi_data_digest data_digest; // Optional data digest +} iscsi_nop_in_packet; + +#define ISCSI_VALIDATE_PACKET_RESULT_OK 0L // Validation successful -> iSCSI packet recognized and compliance to protocol specification +#define ISCSI_VALIDATE_PACKET_RESULT_ERROR_NO_DATA -1L // Validation failed -> No packet data specified +#define ISCSI_VALIDATE_PACKET_RESULT_ERROR_SIZE_TOO_SMALL -2L // Validation failed -> Packet size smaller than smallest possible iSCSI packet +#define ISCSI_VALIDATE_PACKET_RESULT_ERROR_SIZE_MISMATCH -3L // Validation failed -> Packet size doesn't match calculated lengths from BHS +#define ISCSI_VALIDATE_PACKET_RESULT_ERROR_UNSUPPORTED_VERSION -4L // Validation failed -> iSCSI protocol version not supported yet +#define ISCSI_VALIDATE_PACKET_RESULT_ERROR_PROTOCOL_SPECS -5L // Validation failed -> Valid opcode but violates iSCSI protocol specification +#define ISCSI_VALIDATE_PACKET_RESULT_ERROR_INVALID_OPCODE -6L // Validation failed -> Invalid opcode according to iSCSI protocol specification +#define ISCSI_VALIDATE_PACKET_RESULT_ERROR_CRC32C_HDR_DIGEST -7L // Validation failed -> CRC32C check failed for header (BHS and/or AHS) +#define ISCSI_VALIDATE_PACKET_RESULT_ERROR_CRC32C_DATA_DIGEST -8L // Validation failed -> CRC32C check failed for data segment + +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_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 + +#define ISCSI_TEXT_KEY_MAX_LEN 63UL // Maximum length of a key according to iSCSI specs + +#define ISCSI_TEXT_VALUE_MAX_SIMPLE_LEN 255UL // Maximum length of value for a simple key type +#define ISCSI_TEXT_VALUE_MAX_LEN 8192UL // Maximum length of value for a normal key + +#define ISCSI_TEXT_KEY_VALUE_PAIR_TYPE_INVALID -1L // Invalid +#define ISCSI_TEXT_KEY_VALUE_PAIR_TYPE_UNSPECIFIED 0L // Unspecified type +#define ISCSI_TEXT_KEY_VALUE_PAIR_TYPE_LIST 1L // List +#define ISCSI_TEXT_KEY_VALUE_PAIR_TYPE_NUM_MIN 2L // Numerical minimum +#define ISCSI_TEXT_KEY_VALUE_PAIR_TYPE_NUM_MAX 3L // Numerical maximum +#define ISCSI_TEXT_KEY_VALUE_PAIR_TYPE_NUM_DECLARATIVE 4L // Numerical declarative +#define ISCSI_TEXT_KEY_VALUE_PAIR_TYPE_DECLARATIVE 5L // Declarative +#define ISCSI_TEXT_KEY_VALUE_PAIR_TYPE_BOOL_OR 6L // Boolean OR +#define ISCSI_TEXT_KEY_VALUE_PAIR_TYPE_BOOL_AND 7L // Boolean AND + +typedef struct iscsi_key_value_pair { + int type; // Type of pair (see above) + int state_index; + uint8_t *value; // Value of key +} iscsi_key_value_pair; + +static int iscsi_parse_text_key_value_pair(iscsi_hashmap *pairs, const uint8_t *packet_data, const uint32_t len); // Extracts a single 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 cbit, uint8_t **partial_pair); // Extracts all text key / value pairs out of an iSCSI packet into a hash map + +typedef struct iscsi_connection { + iscsi_hashmap *key_value_pairs; // Hash map containing text key / value pairs associated to this connection + int header_digest; // iSCSI connection contains a header digest (CRC32), must be 0 or 4 for now + int data_digest; // iSCSI connection contains a data digest (CRC32), must be 0 or 4 for now + uint8_t opcode; // Always 0x03 according to specification (see above) + int8_t flags; // Login request flags (see above) + uint8_t version_max; // Version-max indicates the maximum version number supported. + // All Login Requests within the Login Phase MUST carry the same + // Version-max. Currently, this is always 0 + // The target MUST use the value presented with the first Login Request. + uint8_t version_min; // All Login Requests within the Login Phase MUST carry the same + // Version-min. The target MUST use the value presented with the first + // Login Request. Always 0 for now + struct iscsi_isid isid; // ISID (see above for declaration) + uint16_t tsih; // The TSIH must be set in the first Login Request. The reserved value + // 0 MUST be used on the first connection for a new session. Otherwise, + // the TSIH sent by the target at the conclusion of the successful login + // of the first connection for this session MUST be used. The TSIH + // identifies to the target the associated existing session for this new + // connection. + // All Login Requests within a Login Phase MUST carry the same TSIH. + // The target MUST check the value presented with the first Login + // Request + uint32_t init_task_tag; // Initiator task tag + uint16_t cid; // Connection ID. The CID provides a unique ID for this connection within the session. + // All Login Requests within the Login Phase MUST carry the same CID. + // The target MUST use the value presented with the first Login Request. + // A Login Request with a non-zero TSIH and a CID equal to that of an + // existing connection implies a logout of the connection followed by a + // login + uint32_t cmd_sn; // The CmdSN is either the initial command sequence number of a session + // (for the first Login Request of a session - the "leading" login) or + // the command sequence number in the command stream if the login is for + // a new connection in an existing session. + // Examples: + // - Login on a leading connection: If the leading login carries the + // CmdSN 123, all other Login Requests in the same Login Phase carry + // the CmdSN 123, and the first non-immediate command in the Full + // Feature Phase also carries the CmdSN 123. + // - Login on other than a leading connection: If the current CmdSN at + // the time the first login on the connection is issued is 500, then + // that PDU carries CmdSN=500. Subsequent Login Requests that are + // needed to complete this Login Phase may carry a CmdSN higher than + // 500 if non-immediate requests that were issued on other connections + // in the same session advance the CmdSN. + // If the Login Request is a leading Login Request, the target MUST use + // the value presented in the CmdSN as the target value for the + // ExpCmdSN + uint32_t exp_stat_sn; // For the first Login Request on a connection, this is the ExpStatSN + // for the old connection, and this field is only valid if the Login + // Request restarts a connection + // For subsequent Login Requests, it is used to acknowledge the Login + // Responses with their increasing StatSN values +} 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 +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 + +#endif /* DNBD3_ISCSI_H_ */ |
