Add support for Data Digest

This commit is contained in:
Brian Meagher
2024-05-04 18:54:21 -07:00
parent ec5d33da63
commit 882bcad53a
11 changed files with 254 additions and 17 deletions

View File

@@ -45,6 +45,7 @@ Username and password for bidirectional CHAP authentication:
target_user=<account>
target_password=<password>
header_digest=<crc32c|none>
data_digest=<crc32c|none>
Transport:
iser
@@ -125,6 +126,15 @@ be overridden by an application by calling iscsi_set_header_digest() if the
application wants to force a specific setting.
Data Digest
===========
Libiscsi supports DataDigest. By default, libiscsi will offer None so that
Data digest will not be used, no matter what the target setting is. This can
be overridden by an application by calling iscsi_set_data_digest() if the
application wants to force a specific setting.
Patches
=======

View File

@@ -61,6 +61,13 @@ struct iscsi_in_pdu {
long long data_pos;
unsigned char *data;
/*
* Some data structures wrt Data Digest (if negociated)
*/
unsigned char data_digest_buf[ISCSI_DIGEST_SIZE];
int received_data_digest_bytes;
uint32_t calculated_data_digest;
};
void iscsi_free_iscsi_in_pdu(struct iscsi_context *iscsi, struct iscsi_in_pdu *in);
@@ -105,6 +112,8 @@ struct iscsi_context {
uint32_t statsn;
enum iscsi_header_digest want_header_digest;
enum iscsi_header_digest header_digest;
enum iscsi_data_digest want_data_digest;
enum iscsi_data_digest data_digest;
int fd;
int is_connected;
@@ -272,6 +281,8 @@ struct iscsi_pdu {
struct iscsi_scsi_cbdata scsi_cbdata;
time_t scsi_timeout;
uint32_t expxferlen;
uint32_t calculated_data_digest;
};
struct iscsi_pdu *iscsi_allocate_pdu(struct iscsi_context *iscsi,
@@ -350,6 +361,9 @@ void* iscsi_szmalloc(struct iscsi_context *iscsi, size_t size);
void iscsi_sfree(struct iscsi_context *iscsi, void* ptr);
uint32_t crc32c(uint8_t *buf, int len);
void crc32c_init(uint32_t *crc_ptr);
uint32_t crc32c_chain(uint32_t crc, uint8_t *buf, int len);
uint32_t crc32c_chain_done(uint32_t crc);
struct scsi_task *iscsi_scsi_get_task_from_pdu(struct iscsi_pdu *pdu);

View File

@@ -335,6 +335,29 @@ enum iscsi_header_digest {
EXTERN int iscsi_set_header_digest(struct iscsi_context *iscsi,
enum iscsi_header_digest header_digest);
/*
* Types of data digest we support. Default is NONE
*/
enum iscsi_data_digest {
ISCSI_DATA_DIGEST_NONE = 0,
ISCSI_DATA_DIGEST_NONE_CRC32C = 1,
ISCSI_DATA_DIGEST_CRC32C_NONE = 2,
ISCSI_DATA_DIGEST_CRC32C = 3,
ISCSI_DATA_DIGEST_LAST = ISCSI_DATA_DIGEST_CRC32C
};
/*
* Set the desired data digest for a scsi context.
* Data digest can only be set/changed before the context
* is logged in to the target.
*
* Returns:
* 0: success
* <0: error
*/
EXTERN int iscsi_set_data_digest(struct iscsi_context *iscsi,
enum iscsi_data_digest data_digest);
/*
* Specify the username and password to use for chap authentication
*/

View File

@@ -431,6 +431,7 @@ static int reconnect(struct iscsi_context *iscsi, int force)
iscsi_set_targetname(tmp_iscsi, iscsi->target_name);
iscsi_set_header_digest(tmp_iscsi, iscsi->want_header_digest);
iscsi_set_data_digest(tmp_iscsi, iscsi->want_data_digest);
iscsi_set_initiator_username_pwd(tmp_iscsi, iscsi->user, iscsi->passwd);
iscsi_set_target_username_pwd(tmp_iscsi, iscsi->target_user, iscsi->target_passwd);

View File

@@ -118,3 +118,22 @@ uint32_t crc32c(uint8_t *buf, int len)
return crc^0xffffffff;
}
void crc32c_init(uint32_t *crc_ptr)
{
if (crc_ptr)
*crc_ptr = 0xffffffff;
}
uint32_t crc32c_chain(uint32_t crc, uint8_t *buf, int len)
{
while (len-- > 0) {
crc = (crc>>8) ^ crctable[(crc ^ (*buf++)) & 0xFF];
}
return crc;
}
uint32_t crc32c_chain_done(uint32_t crc)
{
return crc^0xffffffff;
}

View File

@@ -244,6 +244,7 @@ iscsi_create_context(const char *initiator_name)
iscsi->want_immediate_data = ISCSI_IMMEDIATE_DATA_YES;
iscsi->use_immediate_data = ISCSI_IMMEDIATE_DATA_YES;
iscsi->want_header_digest = ISCSI_HEADER_DIGEST_NONE_CRC32C;
iscsi->want_data_digest = ISCSI_DATA_DIGEST_NONE;
iscsi->tcp_keepcnt=3;
iscsi->tcp_keepintvl=30;
@@ -492,6 +493,25 @@ iscsi_set_header_digest(struct iscsi_context *iscsi,
return 0;
}
int
iscsi_set_data_digest(struct iscsi_context *iscsi,
enum iscsi_data_digest data_digest)
{
if (iscsi->is_loggedin) {
iscsi_set_error(iscsi, "trying to set data digest while "
"logged in");
return -1;
}
if ((unsigned)data_digest > ISCSI_DATA_DIGEST_LAST) {
iscsi_set_error(iscsi, "invalid data digest value");
return -1;
}
iscsi->want_data_digest = data_digest;
return 0;
}
int
iscsi_is_logged_in(struct iscsi_context *iscsi)
{
@@ -602,19 +622,32 @@ iscsi_parse_url(struct iscsi_context *iscsi, const char *url, int full)
if (value != NULL) {
*value++ = 0;
}
if (!strcmp(key, "header_digest")) {
if (!strcmp(value, "crc32c")) {
iscsi_set_header_digest(
iscsi, ISCSI_HEADER_DIGEST_CRC32C);
} else if (!strcmp(value, "none")) {
iscsi_set_header_digest(
iscsi, ISCSI_HEADER_DIGEST_NONE);
} else {
iscsi_set_error(iscsi,
"Invalid URL argument for header_digest: %s", value);
return NULL;
}
}
if (!strcmp(key, "header_digest")) {
if (!strcmp(value, "crc32c")) {
iscsi_set_header_digest(
iscsi, ISCSI_HEADER_DIGEST_CRC32C);
} else if (!strcmp(value, "none")) {
iscsi_set_header_digest(
iscsi, ISCSI_HEADER_DIGEST_NONE);
} else {
iscsi_set_error(iscsi,
"Invalid URL argument for header_digest: %s", value);
return NULL;
}
}
if (!strcmp(key, "data_digest")) {
if (!strcmp(value, "crc32c")) {
iscsi_set_data_digest(
iscsi, ISCSI_DATA_DIGEST_CRC32C);
} else if (!strcmp(value, "none")) {
iscsi_set_data_digest(
iscsi, ISCSI_DATA_DIGEST_NONE);
} else {
iscsi_set_error(iscsi,
"Invalid URL argument for data_digest: %s", value);
return NULL;
}
}
if (!strcmp(key, "target_user")) {
target_user = value;
} else if (!strcmp(key, "target_password")) {

View File

@@ -115,6 +115,7 @@ iscsi_set_initial_r2t
iscsi_set_log_level
iscsi_set_log_fn
iscsi_set_header_digest
iscsi_set_data_digest
iscsi_set_initiator_username_pwd
iscsi_set_isid_en
iscsi_set_isid_oui

View File

@@ -116,6 +116,7 @@ iscsi_set_alias
iscsi_set_bind_interfaces
iscsi_set_cache_allocations
iscsi_set_header_digest
iscsi_set_data_digest
iscsi_set_immediate_data
iscsi_set_initial_r2t
iscsi_set_initiator_username_pwd

View File

@@ -206,7 +206,24 @@ iscsi_login_add_datadigest(struct iscsi_context *iscsi, struct iscsi_pdu *pdu)
return 0;
}
strncpy(str,"DataDigest=None",MAX_STRING_SIZE);
switch (iscsi->want_data_digest) {
case ISCSI_DATA_DIGEST_NONE:
strncpy(str,"DataDigest=None",MAX_STRING_SIZE);
break;
case ISCSI_DATA_DIGEST_NONE_CRC32C:
strncpy(str,"DataDigest=None,CRC32C",MAX_STRING_SIZE);
break;
case ISCSI_DATA_DIGEST_CRC32C_NONE:
strncpy(str,"DataDigest=CRC32C,None",MAX_STRING_SIZE);
break;
case ISCSI_DATA_DIGEST_CRC32C:
strncpy(str,"DataDigest=CRC32C",MAX_STRING_SIZE);
break;
default:
iscsi_set_error(iscsi, "invalid data digest value");
return -1;
}
if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1)
!= 0) {
iscsi_set_error(iscsi, "Out-of-memory: pdu add data failed.");
@@ -1223,6 +1240,16 @@ iscsi_process_login_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu,
}
}
if (!strncmp(ptr, "DataDigest=", 11)) {
if (!strcmp(ptr + 11, "CRC32C")) {
iscsi->want_data_digest
= ISCSI_DATA_DIGEST_CRC32C;
} else {
iscsi->want_data_digest
= ISCSI_DATA_DIGEST_NONE;
}
}
if (!strncmp(ptr, "FirstBurstLength=", 17)) {
iscsi->first_burst_length = strtol(ptr + 17, NULL, 10);
}
@@ -1393,6 +1420,7 @@ iscsi_process_login_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu,
iscsi->is_loggedin = 1;
iscsi_itt_post_increment(iscsi);
iscsi->header_digest = iscsi->want_header_digest;
iscsi->data_digest = iscsi->want_data_digest;
ISCSI_LOG(iscsi, 2, "login successful");
pdu->callback(iscsi, SCSI_STATUS_GOOD, NULL, pdu->private_data);
} else {

View File

@@ -225,6 +225,9 @@ iscsi_allocate_pdu(struct iscsi_context *iscsi, enum iscsi_opcode opcode,
/* flags */
pdu->flags = flags;
/* DataDigest - may or may not be calculated. Initialize anyway. */
crc32c_init(&pdu->calculated_data_digest);
return pdu;
}
@@ -537,6 +540,25 @@ iscsi_process_pdu(struct iscsi_context *iscsi, struct iscsi_in_pdu *in)
}
}
/* verify data checksum ... */
if (iscsi->data_digest != ISCSI_DATA_DIGEST_NONE) {
int dsl = scsi_get_uint32(&in->hdr[4]) & 0x00ffffff;
/* ... but only if some data is present. */
if (dsl) {
uint32_t crc_rcvd = 0;
uint32_t crc = crc32c_chain_done(in->calculated_data_digest);
crc_rcvd |= in->data_digest_buf[0];
crc_rcvd |= in->data_digest_buf[1] << 8;
crc_rcvd |= in->data_digest_buf[2] << 16;
crc_rcvd |= in->data_digest_buf[3] << 24;
if (crc != crc_rcvd) {
iscsi_set_error(iscsi, "data checksum verification failed: calculated 0x%" PRIx32 " received 0x%" PRIx32, crc, crc_rcvd);
return -1;
}
}
}
if (ahslen != 0) {
iscsi_set_error(iscsi, "cant handle expanded headers yet");
return -1;

View File

@@ -62,6 +62,7 @@
#include <sys/uio.h>
#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -514,7 +515,7 @@ iscsi_out_queue_length(struct iscsi_context *iscsi)
}
ssize_t
iscsi_iovector_readv_writev(struct iscsi_context *iscsi, struct scsi_iovector *iovector, uint32_t pos, ssize_t count, int do_write)
iscsi_iovector_readv_writev(struct iscsi_context *iscsi, struct scsi_iovector *iovector, uint32_t pos, ssize_t count, uint32_t *data_digest_ptr, int do_write)
{
struct scsi_iovec *iov, *iov2;
int niov;
@@ -598,6 +599,19 @@ iscsi_iovector_readv_writev(struct iscsi_context *iscsi, struct scsi_iovector *i
n = readv(iscsi->fd, (struct iovec*) iov, niov);
}
/* Update the data digest */
if (data_digest_ptr && n > 0) {
int i;
size_t bytes_to_crc = n;
struct iovec *iov_ptr = (struct iovec*)iov;
for ( i=0; iov_ptr && i<niov && bytes_to_crc; iov_ptr++, i++) {
size_t chunk = MIN(bytes_to_crc, iov_ptr->iov_len);
*data_digest_ptr = crc32c_chain(*data_digest_ptr, iov_ptr->iov_base, chunk);
bytes_to_crc -= chunk;
}
}
/* restore original values */
iov->iov_base = (void*) ((uintptr_t)iov->iov_base - pos);
iov->iov_len += pos;
@@ -619,6 +633,7 @@ iscsi_read_from_socket(struct iscsi_context *iscsi)
{
struct iscsi_in_pdu *in;
ssize_t hdr_size, data_size, count, padding_size;
bool do_data_digest = (iscsi->data_digest != ISCSI_DATA_DIGEST_NONE);
do {
hdr_size = ISCSI_HEADER_SIZE(iscsi->header_digest);
@@ -628,6 +643,7 @@ iscsi_read_from_socket(struct iscsi_context *iscsi)
iscsi_set_error(iscsi, "Out-of-memory: failed to malloc iscsi_in_pdu");
return -1;
}
crc32c_init(&(iscsi->incoming->calculated_data_digest));
iscsi->incoming->hdr = iscsi_smalloc(iscsi, hdr_size);
if (iscsi->incoming->hdr == NULL) {
iscsi_set_error(iscsi, "Out-of-memory");
@@ -682,7 +698,7 @@ iscsi_read_from_socket(struct iscsi_context *iscsi)
iovector_in = iscsi_get_scsi_task_iovector_in(iscsi, in);
if (iovector_in != NULL && count > padding_size) {
uint32_t offset = scsi_get_uint32(&in->hdr[40]);
count = iscsi_iovector_readv_writev(iscsi, iovector_in, in->data_pos + offset, count - padding_size, 0);
count = iscsi_iovector_readv_writev(iscsi, iovector_in, in->data_pos + offset, count - padding_size, do_data_digest ? &(in->calculated_data_digest) : NULL, 0);
} else {
if (iovector_in == NULL) {
if (in->data == NULL) {
@@ -695,6 +711,8 @@ iscsi_read_from_socket(struct iscsi_context *iscsi)
buf = &in->data[in->data_pos];
}
count = recv(iscsi->fd, (void *)buf, count, 0);
if (do_data_digest && count > 0)
in->calculated_data_digest = crc32c_chain(in->calculated_data_digest, buf, count);
}
if (count == 0) {
/* remote side has closed the socket. */
@@ -713,6 +731,28 @@ iscsi_read_from_socket(struct iscsi_context *iscsi)
break;
}
/* Handle Data Digest receive */
if (data_size != 0 && do_data_digest &&
in->received_data_digest_bytes < ISCSI_DIGEST_SIZE) {
count = recv(iscsi->fd, (void *)(in->data_digest_buf + in->received_data_digest_bytes), ISCSI_DIGEST_SIZE - in->received_data_digest_bytes, 0);
if (count == 0) {
/* remote side has closed the socket. */
return -1;
}
if (count < 0) {
if (errno == EINTR || errno == EAGAIN) {
break;
}
return -1;
}
in->received_data_digest_bytes += count;
if (in->received_data_digest_bytes < ISCSI_DIGEST_SIZE) {
break;
}
}
iscsi->incoming = NULL;
if (iscsi_process_pdu(iscsi, in) != 0) {
iscsi_free_iscsi_in_pdu(iscsi, in);
@@ -751,6 +791,7 @@ iscsi_write_to_socket(struct iscsi_context *iscsi)
struct iscsi_pdu *pdu;
static char padding_buf[3];
int socket_flags = 0;
bool do_data_digest = (iscsi->data_digest != ISCSI_DATA_DIGEST_NONE);
#ifdef MSG_NOSIGNAL
socket_flags |= MSG_NOSIGNAL;
@@ -848,7 +889,7 @@ iscsi_write_to_socket(struct iscsi_context *iscsi)
count = iscsi_iovector_readv_writev(iscsi,
iovector_out,
pdu->payload_offset + pdu->payload_written,
pdu->payload_len - pdu->payload_written, 1);
pdu->payload_len - pdu->payload_written, do_data_digest ? &(pdu->calculated_data_digest) : NULL, 1);
if (count == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return 0;
@@ -873,12 +914,56 @@ iscsi_write_to_socket(struct iscsi_context *iscsi)
"socket :%d", errno);
return -1;
}
if (do_data_digest)
pdu->calculated_data_digest = crc32c_chain(pdu->calculated_data_digest, (uint8_t *)padding_buf, count);
pdu->payload_written += count;
}
/* if we havent written the full padding yet. */
if (pdu->payload_written < total) {
return 0;
}
/*
* Maybe update the total again, and write the digest, but only if
* 1. DataDigest has been negociated, and
* 2. We have actually written some data
*/
if (do_data_digest && pdu->payload_written) {
uint32_t data_digest = crc32c_chain_done(pdu->calculated_data_digest);
char data_digest_buf[ISCSI_DIGEST_SIZE];
total += ISCSI_DIGEST_SIZE;
data_digest_buf[3] = (data_digest >> 24);
data_digest_buf[2] = (data_digest >> 16);
data_digest_buf[1] = (data_digest >> 8);
data_digest_buf[0] = (data_digest);
/* Write data digest */
if (pdu->payload_written < total) {
int todo = total - pdu->payload_written;
count = send(iscsi->fd, data_digest_buf + (ISCSI_DIGEST_SIZE - todo), todo, socket_flags);
if (count == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
return 0;
}
iscsi_set_error(iscsi, "Error when writing to "
"socket :%d", errno);
return -1;
}
pdu->payload_written += count;
}
}
/* if we havent written everything yet. */
if (pdu->payload_written != total) {
return 0;
}
if (pdu->flags & ISCSI_PDU_CORK_WHEN_SENT) {
iscsi->is_corked = 1;
}