diff --git a/README.md b/README.md index 15f9de6..572be0a 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ Username and password for bidirectional CHAP authentication: target_user= target_password= header_digest= +data_digest= 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 ======= diff --git a/include/iscsi-private.h b/include/iscsi-private.h index 82a328f..63ea17d 100644 --- a/include/iscsi-private.h +++ b/include/iscsi-private.h @@ -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); diff --git a/include/iscsi.h b/include/iscsi.h index 8196f64..af557e7 100644 --- a/include/iscsi.h +++ b/include/iscsi.h @@ -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 */ diff --git a/lib/connect.c b/lib/connect.c index 5234e46..1960ef2 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -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); diff --git a/lib/crc32c.c b/lib/crc32c.c index 10180ba..313cad0 100644 --- a/lib/crc32c.c +++ b/lib/crc32c.c @@ -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; +} + diff --git a/lib/init.c b/lib/init.c index 6bcc761..cb3baeb 100644 --- a/lib/init.c +++ b/lib/init.c @@ -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")) { diff --git a/lib/libiscsi.def b/lib/libiscsi.def index c56fe59..6b59726 100644 --- a/lib/libiscsi.def +++ b/lib/libiscsi.def @@ -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 diff --git a/lib/libiscsi.syms.in b/lib/libiscsi.syms.in index 2eb50a7..daf2b0f 100644 --- a/lib/libiscsi.syms.in +++ b/lib/libiscsi.syms.in @@ -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 diff --git a/lib/login.c b/lib/login.c index 5177201..268c409 100644 --- a/lib/login.c +++ b/lib/login.c @@ -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 { diff --git a/lib/pdu.c b/lib/pdu.c index 5a97e43..3ef9871 100644 --- a/lib/pdu.c +++ b/lib/pdu.c @@ -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; diff --git a/lib/socket.c b/lib/socket.c index db7f667..d8fbfc9 100644 --- a/lib/socket.c +++ b/lib/socket.c @@ -62,6 +62,7 @@ #include #endif +#include #include #include #include @@ -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 && iiov_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; }