diff --git a/examples/iscsi-dd.c b/examples/iscsi-dd.c index 998477e..cd09650 100644 --- a/examples/iscsi-dd.c +++ b/examples/iscsi-dd.c @@ -23,34 +23,39 @@ #include #include #include +#include #include "iscsi.h" #include "scsi-lowlevel.h" const char *initiator = "iqn.2010-11.ronnie:iscsi-inq"; -int max_in_flight = 50; -int blocks_per_io = 200; +uint32_t max_in_flight = 50; +uint32_t blocks_per_io = 200; struct client { int finished; - int in_flight; + uint32_t in_flight; struct iscsi_context *src_iscsi; int src_lun; int src_blocksize; uint64_t src_num_blocks; + struct scsi_inquiry_device_designator src_tgt_desig; uint64_t pos; struct iscsi_context *dst_iscsi; int dst_lun; int dst_blocksize; uint64_t dst_num_blocks; + struct scsi_inquiry_device_designator dst_tgt_desig; int use_16_for_rw; + int use_xcopy; int progress; int ignore_errors; }; void fill_read_queue(struct client *client); +void fill_xcopy_queue(struct client *client); struct write_task { struct scsi_task *rt; @@ -153,7 +158,7 @@ void read_cb(struct iscsi_context *iscsi _U_, int status, void *command_data, vo void fill_read_queue(struct client *client) { - int num_blocks; + uint32_t num_blocks; while(client->in_flight < max_in_flight && client->pos < client->src_num_blocks) { struct scsi_task *task; @@ -185,14 +190,343 @@ void fill_read_queue(struct client *client) } } +int populate_tgt_desc(unsigned char *desc, + struct scsi_inquiry_device_designator *tgt_desig, + int rel_init_port_id, uint32_t block_size) +{ + desc[0] = IDENT_DESCR_TGT_DESCR; + desc[1] = 0; /* peripheral type */ + desc[2] = (rel_init_port_id >> 8) & 0xFF; + desc[3] = rel_init_port_id & 0xFF; + desc[4] = tgt_desig->code_set; + desc[5] = (tgt_desig->designator_type & 0xF) + | ((tgt_desig->association & 3) << 4); + desc[7] = tgt_desig->designator_length; + memcpy(desc + 8, tgt_desig->designator, tgt_desig->designator_length); + + desc[28] = 0; + desc[29] = (block_size >> 16) & 0xFF; + desc[30] = (block_size >> 8) & 0xFF; + desc[31] = block_size & 0xFF; + + return 32; +} + +int populate_seg_desc_hdr(unsigned char *hdr, int dc, int cat, int src_index, + int dst_index) +{ + int desc_len = 28; + + hdr[0] = BLK_TO_BLK_SEG_DESCR; + hdr[1] = ((dc << 1) | cat) & 0xFF; + hdr[2] = (desc_len >> 8) & 0xFF; + hdr[3] = (desc_len - SEG_DESC_SRC_INDEX_OFFSET) & 0xFF; /* don't account for the first 4 bytes in descriptor header*/ + hdr[4] = (src_index >> 8) & 0xFF; + hdr[5] = src_index & 0xFF; + hdr[6] = (dst_index >> 8) & 0xFF; + hdr[7] = dst_index & 0xFF; + + return desc_len; +} + +int populate_seg_desc_b2b(unsigned char *desc, int dc, int cat, + int src_index, int dst_index, int num_blks, + uint64_t src_lba, uint64_t dst_lba) +{ + int desc_len = populate_seg_desc_hdr(desc, dc, cat, + src_index, dst_index); + + desc[10] = (num_blks >> 8) & 0xFF; + desc[11] = num_blks & 0xFF; + desc[12] = (src_lba >> 56) & 0xFF; + desc[13] = (src_lba >> 48) & 0xFF; + desc[14] = (src_lba >> 40) & 0xFF; + desc[15] = (src_lba >> 32) & 0xFF; + desc[16] = (src_lba >> 24) & 0xFF; + desc[17] = (src_lba >> 16) & 0xFF; + desc[18] = (src_lba >> 8) & 0xFF; + desc[19] = src_lba & 0xFF; + desc[20] = (dst_lba >> 56) & 0xFF; + desc[21] = (dst_lba >> 48) & 0xFF; + desc[22] = (dst_lba >> 40) & 0xFF; + desc[23] = (dst_lba >> 32) & 0xFF; + desc[24] = (dst_lba >> 24) & 0xFF; + desc[25] = (dst_lba >> 16) & 0xFF; + desc[26] = (dst_lba >> 8) & 0xFF; + desc[27] = dst_lba & 0xFF; + + return desc_len; +} + +void populate_param_header(unsigned char *buf, int list_id, int str, int list_id_usage, int prio, int tgt_desc_len, int seg_desc_len, int inline_data_len) +{ + buf[0] = list_id; + buf[1] = ((str & 1) << 5) | ((list_id_usage & 3) << 3) | (prio & 7); + buf[2] = (tgt_desc_len >> 8) & 0xFF; + buf[3] = tgt_desc_len & 0xFF; + buf[8] = (seg_desc_len >> 24) & 0xFF; + buf[9] = (seg_desc_len >> 16) & 0xFF; + buf[10] = (seg_desc_len >> 8) & 0xFF; + buf[11] = seg_desc_len & 0xFF; + buf[12] = (inline_data_len >> 24) & 0xFF; + buf[13] = (inline_data_len >> 16) & 0xFF; + buf[14] = (inline_data_len >> 8) & 0xFF; + buf[15] = inline_data_len & 0xFF; +} + +void xcopy_cb(struct iscsi_context *iscsi _U_, int status, void *command_data, void *private_data) +{ + struct client *client = (struct client *)private_data; + struct scsi_task *task = command_data; + + if (status == SCSI_STATUS_CHECK_CONDITION) { + printf("XCOPY failed with sense key:%d ascq:%04x\n", + task->sense.key, task->sense.ascq); + scsi_free_scsi_task(task); + exit(10); + } + + if (status != SCSI_STATUS_GOOD) { + printf("XCOPY failed with %s\n", iscsi_get_error(iscsi)); + if (!client->ignore_errors) { + scsi_free_scsi_task(task); + exit(10); + } + } + + client->in_flight--; + fill_xcopy_queue(client); + + if (client->progress) { + printf("\r%"PRIu64" of %"PRIu64" blocks transferred.", + client->pos, client->src_num_blocks); + } + + if ((client->in_flight == 0) && (client->pos == client->src_num_blocks)) { + client->finished = 1; + if (client->progress) { + printf("\n"); + } + } + scsi_free_scsi_task(task); +} + +void fill_xcopy_queue(struct client *client) +{ + while (client->in_flight < max_in_flight && client->pos < client->src_num_blocks) { + struct scsi_task *task; + struct iscsi_data data; + unsigned char *xcopybuf; + int offset; + uint32_t num_blocks; + int tgt_desc_len; + int seg_desc_len; + + client->in_flight++; + + num_blocks = client->src_num_blocks - client->pos; + if (num_blocks > blocks_per_io) { + num_blocks = blocks_per_io; + } + + data.size = XCOPY_DESC_OFFSET + + 32 * 2 + /* IDENT_DESCR_TGT_DESCR */ + 28; /* BLK_TO_BLK_SEG_DESCR */ + data.data = malloc(data.size); + if (data.data == NULL) { + printf("failed to alloc XCOPY buffer\n"); + exit(10); + } + + xcopybuf = data.data; + memset(xcopybuf, 0, data.size); + + /* Initialise CSCD list with one src + one dst descriptor */ + offset = XCOPY_DESC_OFFSET; + offset += populate_tgt_desc(xcopybuf + offset, + &client->src_tgt_desig, + 0, client->src_blocksize); + offset += populate_tgt_desc(xcopybuf + offset, + &client->dst_tgt_desig, + 0, client->dst_blocksize); + tgt_desc_len = offset - XCOPY_DESC_OFFSET; + + /* Initialise one segment descriptor */ + seg_desc_len = populate_seg_desc_b2b(xcopybuf + offset, 0, 0, + 0, 1, num_blocks, client->pos, client->pos); + offset += seg_desc_len; + + /* Initialise the parameter list header */ + populate_param_header(xcopybuf, 1, 0, LIST_ID_USAGE_DISCARD, 0, + tgt_desc_len, seg_desc_len, 0); + + task = iscsi_extended_copy_task(client->src_iscsi, + client->src_lun, + &data, xcopy_cb, client); + if (task == NULL) { + printf("failed to send XCOPY command\n"); + exit(10); + } + + client->pos += num_blocks; + } +} + +void cscd_ident_inq(struct iscsi_context *iscsi, + int lun, + struct scsi_inquiry_device_designator *_tgt_desig) +{ + struct scsi_task *task = NULL; + struct scsi_inquiry_device_identification *inq_di = NULL; + struct scsi_inquiry_device_designator *desig, *tgt_desig = NULL; + enum scsi_designator_type prev_type = 0; + + /* check what type of lun we have */ + task = iscsi_inquiry_sync(iscsi, lun, 1, + SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION, 255); + if (task == NULL || task->status != SCSI_STATUS_GOOD) { + fprintf(stderr, "failed to send inquiry command: %s\n", + iscsi_get_error(iscsi)); + exit(10); + } + + inq_di = scsi_datain_unmarshall(task); + if (inq_di == NULL) { + fprintf(stderr, "failed to unmarshall inquiry datain blob\n"); + exit(10); + } + + for (desig = inq_di->designators; desig; desig = desig->next) { + switch (desig->designator_type) { + case SCSI_DESIGNATOR_TYPE_VENDOR_SPECIFIC: + case SCSI_DESIGNATOR_TYPE_T10_VENDORT_ID: + case SCSI_DESIGNATOR_TYPE_EUI_64: + case SCSI_DESIGNATOR_TYPE_NAA: + if (prev_type <= desig->designator_type) { + tgt_desig = desig; + prev_type = desig->designator_type; + } + default: + continue; + } + } + + if (tgt_desig == NULL) { + fprintf(stderr, "No suitalble target descriptor format found"); + exit(10); + } + + /* copy what's needed for XCOPY */ + _tgt_desig->code_set = tgt_desig->code_set; + _tgt_desig->association = tgt_desig->association; + _tgt_desig->designator_type = tgt_desig->designator_type; + _tgt_desig->designator_length = tgt_desig->designator_length; + _tgt_desig->designator = malloc(tgt_desig->designator_length); + memcpy(_tgt_desig->designator, tgt_desig->designator, tgt_desig->designator_length); + + scsi_free_scsi_task(task); +} + +void cscd_param_check(struct iscsi_context *iscsi, + int lun, + uint32_t blocksize) +{ + struct scsi_task *task = NULL; + struct scsi_copy_results_op_params *opp; + uint32_t io_segment_bytes; + + task = iscsi_receive_copy_results_sync(iscsi, lun, + SCSI_COPY_RESULTS_OP_PARAMS, 0, 1024); + if (task == NULL || task->status != SCSI_STATUS_GOOD) { + fprintf(stderr, "XCOPY RECEIVE COPY RESULTS failed: %s\n", + iscsi_get_error(iscsi)); + exit(10); + } + + opp = scsi_datain_unmarshall(task); + if (opp == NULL) { + fprintf(stderr, "failed to unmarshall XCOPY RCR datain blob\n"); + exit(10); + } + + if (opp->max_target_desc_count < 2) { + fprintf(stderr, "XCOPY max CSCD desc count %d too small\n", + opp->max_target_desc_count); + exit(10); + } + if (opp->max_segment_desc_count < 1) { + fprintf(stderr, "XCOPY max segment desc count %d too small\n", + opp->max_segment_desc_count); + exit(10); + } + + io_segment_bytes = blocks_per_io * blocksize; + if (io_segment_bytes > opp->max_segment_length) { + fprintf(stderr, + "%u bytes per I/O exceeds XCOPY max segment len %u\n", + io_segment_bytes, opp->max_segment_length); + exit(10); + } + if (blocks_per_io > USHRT_MAX) { + fprintf(stderr, + "%u blocks per I/O exceeds XCOPY field width max %u\n", + blocks_per_io, USHRT_MAX); + exit(10); + } + + scsi_free_scsi_task(task); +} + +void readcap(struct iscsi_context *iscsi, int lun, int use_16, + int *_blocksize, uint64_t *_num_blocks) +{ + struct scsi_task *task; + + if (use_16) { + struct scsi_readcapacity16 *rc16; + + task = iscsi_readcapacity16_sync(iscsi, lun); + if (task == NULL || task->status != SCSI_STATUS_GOOD) { + fprintf(stderr, + "failed to send readcapacity command\n"); + exit(10); + } + rc16 = scsi_datain_unmarshall(task); + if (rc16 == NULL) { + fprintf(stderr, + "failed to unmarshall readcapacity16 data\n"); + exit(10); + } + *_blocksize = rc16->block_length; + *_num_blocks = rc16->returned_lba + 1; + } else { + struct scsi_readcapacity10 *rc10; + + task = iscsi_readcapacity10_sync(iscsi, lun, 0, 0); + if (task == NULL || task->status != SCSI_STATUS_GOOD) { + fprintf(stderr, + "failed to send readcapacity command\n"); + exit(10); + } + rc10 = scsi_datain_unmarshall(task); + if (rc10 == NULL) { + fprintf(stderr, + "failed to unmarshall readcapacity10 data\n"); + exit(10); + } + *_blocksize = rc10->block_size; + *_num_blocks = rc10->lba; + } + + scsi_free_scsi_task(task); + return; +} + int main(int argc, char *argv[]) { char *src_url = NULL; char *dst_url = NULL; struct iscsi_url *iscsi_url; - struct scsi_task *task; - struct scsi_readcapacity10 *rc10; - struct scsi_readcapacity16 *rc16; int c; struct pollfd pfd[2]; struct client client; @@ -203,6 +537,7 @@ int main(int argc, char *argv[]) {"initiator-name", required_argument, NULL, 'i'}, {"progress", no_argument, NULL, 'p'}, {"16", no_argument, NULL, '6'}, + {"xcopy", no_argument, NULL, 'x'}, {"max", required_argument, NULL, 'm'}, {"blocks", required_argument, NULL, 'b'}, {"ignore-errors", no_argument, NULL, 'n'}, @@ -214,6 +549,8 @@ int main(int argc, char *argv[]) while ((c = getopt_long(argc, argv, "d:s:i:m:b:p6n", long_options, &option_index)) != -1) { + char *endptr; + switch (c) { case 'd': dst_url = optarg; @@ -230,11 +567,24 @@ int main(int argc, char *argv[]) case '6': client.use_16_for_rw = 1; break; + case 'x': + client.use_xcopy = 1; + break; case 'm': - max_in_flight = atoi(optarg); + max_in_flight = strtoul(optarg, &endptr, 10); + if (*endptr != '\0' || max_in_flight == UINT_MAX) { + fprintf(stderr, "Invalid max in flight: %s\n", + optarg); + exit(10); + } break; case 'b': - blocks_per_io = atoi(optarg); + blocks_per_io = strtoul(optarg, &endptr, 10); + if (*endptr != '\0' || blocks_per_io == UINT_MAX) { + fprintf(stderr, "Invalid blocks per I/O: %s\n", + optarg); + exit(10); + } break; case 'n': client.ignore_errors = 1; @@ -278,34 +628,14 @@ int main(int argc, char *argv[]) client.src_lun = iscsi_url->lun; iscsi_destroy_url(iscsi_url); - if (client.use_16_for_rw) { - task = iscsi_readcapacity16_sync(client.src_iscsi, client.src_lun); - if (task == NULL || task->status != SCSI_STATUS_GOOD) { - fprintf(stderr, "failed to send readcapacity command\n"); - exit(10); - } - rc16 = scsi_datain_unmarshall(task); - if (rc16 == NULL) { - fprintf(stderr, "failed to unmarshall readcapacity16 data\n"); - exit(10); - } - client.src_blocksize = rc16->block_length; - client.src_num_blocks = rc16->returned_lba + 1; - scsi_free_scsi_task(task); - } else { - task = iscsi_readcapacity10_sync(client.src_iscsi, client.src_lun, 0, 0); - if (task == NULL || task->status != SCSI_STATUS_GOOD) { - fprintf(stderr, "failed to send readcapacity command\n"); - exit(10); - } - rc10 = scsi_datain_unmarshall(task); - if (rc10 == NULL) { - fprintf(stderr, "failed to unmarshall readcapacity10 data\n"); - exit(10); - } - client.src_blocksize = rc10->block_size; - client.src_num_blocks = rc10->lba; - scsi_free_scsi_task(task); + readcap(client.src_iscsi, client.src_lun, client.use_16_for_rw, + &client.src_blocksize, &client.src_num_blocks); + + if (client.use_xcopy) { + cscd_ident_inq(client.src_iscsi, client.src_lun, + &client.src_tgt_desig); + cscd_param_check(client.src_iscsi, client.src_lun, + client.src_blocksize); } client.dst_iscsi = iscsi_create_context(initiator); @@ -330,34 +660,14 @@ int main(int argc, char *argv[]) client.dst_lun = iscsi_url->lun; iscsi_destroy_url(iscsi_url); - if (client.use_16_for_rw) { - task = iscsi_readcapacity16_sync(client.dst_iscsi, client.dst_lun); - if (task == NULL || task->status != SCSI_STATUS_GOOD) { - fprintf(stderr, "failed to send readcapacity command\n"); - exit(10); - } - rc16 = scsi_datain_unmarshall(task); - if (rc16 == NULL) { - fprintf(stderr, "failed to unmarshall readcapacity16 data\n"); - exit(10); - } - client.dst_blocksize = rc16->block_length; - client.dst_num_blocks = rc16->returned_lba + 1; - scsi_free_scsi_task(task); - } else { - task = iscsi_readcapacity10_sync(client.dst_iscsi, client.dst_lun, 0, 0); - if (task == NULL || task->status != SCSI_STATUS_GOOD) { - fprintf(stderr, "failed to send readcapacity command\n"); - exit(10); - } - rc10 = scsi_datain_unmarshall(task); - if (rc10 == NULL) { - fprintf(stderr, "failed to unmarshall readcapacity10 data\n"); - exit(10); - } - client.dst_blocksize = rc10->block_size; - client.dst_num_blocks = rc10->lba; - scsi_free_scsi_task(task); + readcap(client.dst_iscsi, client.dst_lun, client.use_16_for_rw, + &client.dst_blocksize, &client.dst_num_blocks); + + if (client.use_xcopy) { + cscd_ident_inq(client.dst_iscsi, client.dst_lun, + &client.dst_tgt_desig); + cscd_param_check(client.dst_iscsi, client.dst_lun, + client.dst_blocksize); } if (client.src_blocksize != client.dst_blocksize) { @@ -370,7 +680,11 @@ int main(int argc, char *argv[]) exit(10); } - fill_read_queue(&client); + if (client.use_xcopy) { + fill_xcopy_queue(&client); + } else { + fill_read_queue(&client); + } while (client.finished == 0) { pfd[0].fd = iscsi_get_fd(client.src_iscsi); diff --git a/include/iscsi-private.h b/include/iscsi-private.h index ad97601..0045f90 100644 --- a/include/iscsi-private.h +++ b/include/iscsi-private.h @@ -350,10 +350,11 @@ void* iscsi_zmalloc(struct iscsi_context *iscsi, size_t size); void* iscsi_realloc(struct iscsi_context *iscsi, void* ptr, size_t size); void iscsi_free(struct iscsi_context *iscsi, void* ptr); char* iscsi_strdup(struct iscsi_context *iscsi, const char* str); +void* iscsi_smalloc(struct iscsi_context *iscsi, size_t size); void* iscsi_szmalloc(struct iscsi_context *iscsi, size_t size); void iscsi_sfree(struct iscsi_context *iscsi, void* ptr); -unsigned long crc32c(char *buf, int len); +uint32_t crc32c(uint8_t *buf, int len); struct scsi_task *iscsi_scsi_get_task_from_pdu(struct iscsi_pdu *pdu); diff --git a/include/iscsi.h b/include/iscsi.h index 30917a0..9b2b85e 100644 --- a/include/iscsi.h +++ b/include/iscsi.h @@ -36,7 +36,7 @@ struct sockaddr; struct scsi_iovec; /* API VERSION */ -#define LIBISCSI_API_VERSION (20160603) +#define LIBISCSI_API_VERSION (20170105) /* FEATURES */ #define LIBISCSI_FEATURE_IOVECTOR (1) @@ -1129,6 +1129,16 @@ iscsi_report_supported_opcodes_task(struct iscsi_context *iscsi, int lun, uint32_t alloc_len, iscsi_command_cb cb, void *private_data); +EXTERN struct scsi_task * +iscsi_receive_copy_results_task(struct iscsi_context *iscsi, int lun, + int sa, int list_id, int alloc_len, + iscsi_command_cb cb, void *private_data); + +EXTERN struct scsi_task * +iscsi_extended_copy_task(struct iscsi_context *iscsi, int lun, + struct iscsi_data *param_data, + iscsi_command_cb cb, void *private_data); + /* * Sync commands for SCSI */ @@ -1454,6 +1464,14 @@ iscsi_report_supported_opcodes_sync(struct iscsi_context *iscsi, int lun, int opcode, int sa, uint32_t alloc_len); +EXTERN struct scsi_task * +iscsi_extended_copy_sync(struct iscsi_context *iscsi, int lun, + struct iscsi_data *param_data); + +EXTERN struct scsi_task * +iscsi_receive_copy_results_sync(struct iscsi_context *iscsi, int lun, + int sa, int list_id, int alloc_len); + /* * These functions are used when the application wants to specify its own buffers to read the data * from the DATA-IN PDUs into, or write the data to DATA-OUT PDUs from. diff --git a/lib/crc32c.c b/lib/crc32c.c index 0ae2cda..4455171 100644 --- a/lib/crc32c.c +++ b/lib/crc32c.c @@ -43,7 +43,7 @@ /* */ /*****************************************************************/ -static unsigned long crctable[256] = { +uint32_t crctable[256] = { 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL, @@ -110,9 +110,9 @@ static unsigned long crctable[256] = { 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L }; -unsigned long crc32c(char *buf, int len) +uint32_t crc32c(uint8_t *buf, int len) { - unsigned long crc = 0xffffffff; + uint32_t crc = 0xffffffff; while (len-- > 0) { crc = (crc>>8) ^ crctable[(crc ^ (*buf++)) & 0xFF]; } diff --git a/lib/init.c b/lib/init.c index 6aa9ea4..9ba9c1d 100644 --- a/lib/init.c +++ b/lib/init.c @@ -111,15 +111,22 @@ char* iscsi_strdup(struct iscsi_context *iscsi, const char* str) { return str2; } -void* iscsi_szmalloc(struct iscsi_context *iscsi, size_t size) { +void* iscsi_smalloc(struct iscsi_context *iscsi, size_t size) { void *ptr; if (size > iscsi->smalloc_size) return NULL; if (iscsi->smalloc_free > 0) { ptr = iscsi->smalloc_ptrs[--iscsi->smalloc_free]; - memset(ptr, 0, iscsi->smalloc_size); iscsi->smallocs++; } else { - ptr = iscsi_zmalloc(iscsi, iscsi->smalloc_size); + ptr = iscsi_malloc(iscsi, iscsi->smalloc_size); + } + return ptr; +} + +void* iscsi_szmalloc(struct iscsi_context *iscsi, size_t size) { + void *ptr = iscsi_smalloc(iscsi, size); + if (ptr) { + memset(ptr, 0, size); } return ptr; } diff --git a/lib/iscsi-command.c b/lib/iscsi-command.c index 9f8c0e8..f9ca8e0 100644 --- a/lib/iscsi-command.c +++ b/lib/iscsi-command.c @@ -2611,6 +2611,52 @@ iscsi_report_supported_opcodes_task(struct iscsi_context *iscsi, int lun, return task; } +struct scsi_task * +iscsi_receive_copy_results_task(struct iscsi_context *iscsi, int lun, + int sa, int list_id, int alloc_len, + iscsi_command_cb cb, void *private_data) +{ + struct scsi_task *task; + + task = scsi_cdb_receive_copy_results(sa, list_id, alloc_len); + if (task == NULL) { + iscsi_set_error(iscsi, "Out-of-memory: Failed to create " + "RECEIVE COPY RESULTS cdb."); + return NULL; + } + + if (iscsi_scsi_command_async(iscsi, lun, task, cb, + NULL, private_data) != 0) { + scsi_free_scsi_task(task); + return NULL; + } + + return task; +} + +struct scsi_task * +iscsi_extended_copy_task(struct iscsi_context *iscsi, int lun, + struct iscsi_data *param_data, + iscsi_command_cb cb, void *private_data) +{ + struct scsi_task *task; + + task = scsi_cdb_extended_copy(param_data->size); + if (task == NULL) { + iscsi_set_error(iscsi, "Out-of-memory: Failed to create " + "EXTENDED COPY cdb."); + return NULL; + } + + if (iscsi_scsi_command_async(iscsi, lun, task, cb, + param_data, private_data) != 0) { + scsi_free_scsi_task(task); + return NULL; + } + + return task; +} + struct scsi_task * iscsi_scsi_get_task_from_pdu(struct iscsi_pdu *pdu) { diff --git a/lib/libiscsi.def b/lib/libiscsi.def index 81ebe60..ea5c940 100644 --- a/lib/libiscsi.def +++ b/lib/libiscsi.def @@ -83,6 +83,10 @@ iscsi_release6_sync iscsi_release6_task iscsi_report_supported_opcodes_sync iscsi_report_supported_opcodes_task +iscsi_extended_copy_sync +iscsi_extended_copy_task +iscsi_receive_copy_results_sync +iscsi_receive_copy_results_task iscsi_reconnect iscsi_sanitize_sync iscsi_sanitize_task diff --git a/lib/libiscsi.syms b/lib/libiscsi.syms index b3f3cac..5d81f44 100644 --- a/lib/libiscsi.syms +++ b/lib/libiscsi.syms @@ -81,6 +81,10 @@ iscsi_release6_sync iscsi_release6_task iscsi_report_supported_opcodes_sync iscsi_report_supported_opcodes_task +iscsi_extended_copy_sync +iscsi_extended_copy_task +iscsi_receive_copy_results_sync +iscsi_receive_copy_results_task iscsi_reconnect iscsi_sanitize_sync iscsi_sanitize_task diff --git a/lib/login.c b/lib/login.c index dd0e7ab..36de085 100644 --- a/lib/login.c +++ b/lib/login.c @@ -1217,6 +1217,8 @@ iscsi_process_login_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, must_have_chap_r = 0; } + ISCSI_LOG(iscsi, 6, "TargetLoginReply: %s", ptr); + ptr += len + 1; size -= len + 1; } diff --git a/lib/pdu.c b/lib/pdu.c index 1275b33..63adcc6 100644 --- a/lib/pdu.c +++ b/lib/pdu.c @@ -36,6 +36,7 @@ #include #include #include +#include #include "iscsi.h" #include "iscsi-private.h" #include "scsi-lowlevel.h" @@ -429,6 +430,20 @@ iscsi_process_pdu(struct iscsi_context *iscsi, struct iscsi_in_pdu *in) uint8_t ahslen = in->hdr[4]; struct iscsi_pdu *pdu; + /* verify header checksum */ + if (iscsi->header_digest != ISCSI_HEADER_DIGEST_NONE) { + uint32_t crc, crc_rcvd = 0; + crc = crc32c(in->hdr, ISCSI_RAW_HEADER_SIZE); + crc_rcvd |= in->hdr[ISCSI_RAW_HEADER_SIZE+0]; + crc_rcvd |= in->hdr[ISCSI_RAW_HEADER_SIZE+1] << 8; + crc_rcvd |= in->hdr[ISCSI_RAW_HEADER_SIZE+2] << 16; + crc_rcvd |= in->hdr[ISCSI_RAW_HEADER_SIZE+3] << 24; + if (crc != crc_rcvd) { + iscsi_set_error(iscsi, "header 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 19c1148..f339979 100644 --- a/lib/socket.c +++ b/lib/socket.c @@ -366,6 +366,7 @@ iscsi_connect_async(struct iscsi_context *iscsi, const char *portal, } freeaddrinfo(ai); + strncpy(iscsi->connected_portal, portal, MAX_STRING_SIZE); return 0; } @@ -669,6 +670,25 @@ iscsi_read_from_socket(struct iscsi_context *iscsi) return 0; } +static int iscsi_pdu_update_headerdigest(struct iscsi_context *iscsi, struct iscsi_pdu *pdu) +{ + uint32_t crc; + + if (pdu->outdata.size < ISCSI_RAW_HEADER_SIZE + ISCSI_DIGEST_SIZE) { + iscsi_set_error(iscsi, "PDU too small (%u) to contain header digest", + (unsigned int) pdu->outdata.size); + return -1; + } + + crc = crc32c(pdu->outdata.data, ISCSI_RAW_HEADER_SIZE); + + pdu->outdata.data[ISCSI_RAW_HEADER_SIZE+3] = (crc >> 24); + pdu->outdata.data[ISCSI_RAW_HEADER_SIZE+2] = (crc >> 16); + pdu->outdata.data[ISCSI_RAW_HEADER_SIZE+1] = (crc >> 8); + pdu->outdata.data[ISCSI_RAW_HEADER_SIZE+0] = (crc); + return 0; +} + static int iscsi_write_to_socket(struct iscsi_context *iscsi) { @@ -718,7 +738,13 @@ iscsi_write_to_socket(struct iscsi_context *iscsi) /* set exp statsn */ iscsi_pdu_set_expstatsn(iscsi->outqueue_current, iscsi->statsn + 1); - + + /* calculate header checksum */ + if (iscsi->header_digest != ISCSI_HEADER_DIGEST_NONE && + iscsi_pdu_update_headerdigest(iscsi, iscsi->outqueue_current) != 0) { + return -1; + } + ISCSI_LIST_REMOVE(&iscsi->outqueue, iscsi->outqueue_current); if (!(iscsi->outqueue_current->flags & ISCSI_PDU_DELETE_WHEN_SENT)) { /* we have to add the pdu to the waitqueue already here @@ -948,23 +974,6 @@ static int iscsi_tcp_queue_pdu(struct iscsi_context *iscsi, return -1; } - if (iscsi->header_digest != ISCSI_HEADER_DIGEST_NONE) { - unsigned long crc; - - if (pdu->outdata.size < ISCSI_RAW_HEADER_SIZE + 4) { - iscsi_set_error(iscsi, "PDU too small (%u) to contain header digest", - (unsigned int) pdu->outdata.size); - return -1; - } - - crc = crc32c((char *)pdu->outdata.data, ISCSI_RAW_HEADER_SIZE); - - pdu->outdata.data[ISCSI_RAW_HEADER_SIZE+3] = (crc >> 24)&0xff; - pdu->outdata.data[ISCSI_RAW_HEADER_SIZE+2] = (crc >> 16)&0xff; - pdu->outdata.data[ISCSI_RAW_HEADER_SIZE+1] = (crc >> 8)&0xff; - pdu->outdata.data[ISCSI_RAW_HEADER_SIZE+0] = (crc) &0xff; - } - iscsi_add_to_outqueue(iscsi, pdu); return 0; @@ -973,7 +982,7 @@ static int iscsi_tcp_queue_pdu(struct iscsi_context *iscsi, void iscsi_free_iscsi_in_pdu(struct iscsi_context *iscsi, struct iscsi_in_pdu *in) { - iscsi_free(iscsi, in->hdr); + iscsi_sfree(iscsi, in->hdr); iscsi_free(iscsi, in->data); in->data=NULL; iscsi_sfree(iscsi, in); diff --git a/lib/sync.c b/lib/sync.c index 9fb78e7..3b907c0 100644 --- a/lib/sync.c +++ b/lib/sync.c @@ -1667,6 +1667,46 @@ iscsi_report_supported_opcodes_sync(struct iscsi_context *iscsi, int lun, return state.task; } +struct scsi_task * +iscsi_receive_copy_results_sync(struct iscsi_context *iscsi, int lun, + int sa, int list_id, int alloc_len) +{ + struct iscsi_sync_state state; + + memset(&state, 0, sizeof(state)); + + if (iscsi_receive_copy_results_task(iscsi, lun, sa, list_id, alloc_len, + scsi_sync_cb, &state) == NULL) { + iscsi_set_error(iscsi, "Failed to send RECEIVE COPY RESULTS" + " command"); + return NULL; + } + + event_loop(iscsi, &state); + + return state.task; +} + +struct scsi_task * +iscsi_extended_copy_sync(struct iscsi_context *iscsi, int lun, + struct iscsi_data *param_data) +{ + struct iscsi_sync_state state; + + memset(&state, 0, sizeof(state)); + + if (iscsi_extended_copy_task(iscsi, lun, param_data, + scsi_sync_cb, &state) == NULL) { + iscsi_set_error(iscsi, "Failed to send EXTENDED COPY" + " command"); + return NULL; + } + + event_loop(iscsi, &state); + + return state.task; +} + struct scsi_task * iscsi_scsi_command_sync(struct iscsi_context *iscsi, int lun, struct scsi_task *task, struct iscsi_data *data) diff --git a/tests/Makefile.am b/tests/Makefile.am index 24abef2..ae99a1b 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -4,7 +4,8 @@ AM_CFLAGS = $(WARN_CFLAGS) LDADD = ../lib/libiscsi.la noinst_PROGRAMS = prog_reconnect prog_reconnect_timeout prog_noop_reply \ - prog_readwrite_iov prog_timeout prog_read_all_pdus + prog_readwrite_iov prog_timeout prog_read_all_pdus \ + prog_header_digest T = `ls test_*.sh` diff --git a/tests/functions.sh b/tests/functions.sh index 34adbe8..066d7c5 100755 --- a/tests/functions.sh +++ b/tests/functions.sh @@ -28,6 +28,10 @@ shutdown_target() { ${TGTADM} --op delete --mode system } +enable_header_digest() { + ${TGTADM} --op update --mode target --tid 1 -n HeaderDigest -v CRC32C +} + create_lun() { # Setup LUN truncate --size=100M ${TGTLUN} diff --git a/tests/prog_header_digest.c b/tests/prog_header_digest.c new file mode 100644 index 0000000..4f39191 --- /dev/null +++ b/tests/prog_header_digest.c @@ -0,0 +1,257 @@ +/* -*- mode:c; tab-width:8; c-basic-offset:8; indent-tabs-mode:nil; -*- */ +/* + Copyright (C) 2015 by Ronnie Sahlberg + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . +*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_POLL_H +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "iscsi.h" +#include "iscsi-private.h" +#include "scsi-lowlevel.h" + +#ifndef discard_const +#define discard_const(ptr) ((void *)((intptr_t)(ptr))) +#endif + +const char *initiator = "iqn.2007-10.com.github:sahlberg:libiscsi:prog-header-digest"; + +struct client_state { + int finished; + int status; + int lun; +}; + +#define TIMER_START(x) gettimeofday(&x, NULL) +#define TIMER_ELAPSED(x, y) do { \ + struct timeval t; \ + int wrap = 0; \ + gettimeofday(&t, NULL); \ + if (t.tv_usec < x.tv_usec) wrap = 1; \ + y.tv_sec = t.tv_sec - x.tv_sec - wrap; \ + y.tv_usec = wrap * 10000000 + t.tv_usec - x.tv_usec; \ + } while(0) + +void event_loop(struct iscsi_context *iscsi, struct client_state *state, + int timeout) +{ + struct pollfd pfd; + struct timeval start_time, elapsed_time; + + TIMER_START(start_time); + while (state->finished == 0) { + pfd.fd = iscsi_get_fd(iscsi); + pfd.events = iscsi_which_events(iscsi); + + if (poll(&pfd, 1, 1000) < 0) { + fprintf(stderr, "Poll failed"); + exit(10); + } + if (iscsi_service(iscsi, pfd.revents) < 0) { + fprintf(stderr, "iscsi_service failed with : %s\n", + iscsi_get_error(iscsi)); + exit(10); + } + TIMER_ELAPSED(start_time, elapsed_time); + if (timeout && elapsed_time.tv_sec > timeout) { + break; + } + } +} + +void tur_cb(struct iscsi_context *iscsi _U_, int status, + void *command_data _U_, void *private_data) +{ + struct client_state *state = (struct client_state *)private_data; + + if (status != 0) { + fprintf(stderr, "TestUnitReady failed\n"); + state->status = status; + } + + state->finished = 1; +} + +void print_usage(void) +{ + fprintf(stderr, "Usage: prog_header_digest [-?|--help] [--usage] " + "[-i|--initiator-name=iqn-name]\n" + "\t\t\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "This command is used to test that if the target " + "disconnects libiscsi will automatically reconnect and " + "re-issue all queued tasks.\n"); +} + +void print_help(void) +{ + fprintf(stderr, "Usage: prog_header_digest [OPTION...] \n"); + fprintf(stderr, " -i, --initiator-name=iqn-name " + "Initiatorname to use\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Help options:\n"); + fprintf(stderr, " -?, --help " + "Show this help message\n"); + fprintf(stderr, " --usage " + "Display brief usage message\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "iSCSI Portal URL format : %s\n", + ISCSI_PORTAL_URL_SYNTAX); + fprintf(stderr, "\n"); + fprintf(stderr, " is either of:\n"); + fprintf(stderr, " \"hostname\" iscsi.example\n"); + fprintf(stderr, " \"ipv4-address\" 10.1.1.27\n"); + fprintf(stderr, " \"ipv6-address\" [fce0::1]\n"); +} + +int main(int argc, char *argv[]) +{ + struct iscsi_context *iscsi; + struct iscsi_url *iscsi_url = NULL; + struct client_state state; + const char *url = NULL; + int c; + static int show_help = 0, show_usage = 0, debug = 0; + + static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"usage", no_argument, NULL, 'u'}, + {"debug", no_argument, NULL, 'd'}, + {"initiator-name", required_argument, NULL, 'i'}, + {0, 0, 0, 0} + }; + int option_index; + + while ((c = getopt_long(argc, argv, "h?uUdi:s", long_options, + &option_index)) != -1) { + switch (c) { + case 'h': + case '?': + show_help = 1; + break; + case 'u': + show_usage = 1; + break; + case 'd': + debug = 1; + break; + case 'i': + initiator = optarg; + break; + default: + fprintf(stderr, "Unrecognized option '%c'\n\n", c); + print_help(); + exit(0); + } + } + + if (show_help != 0) { + print_help(); + exit(0); + } + + if (show_usage != 0) { + print_usage(); + exit(0); + } + + if (optind != argc -1) { + print_usage(); + exit(0); + } + + memset(&state, 0, sizeof(state)); + + if (argv[optind] != NULL) { + url = strdup(argv[optind]); + } + if (url == NULL) { + fprintf(stderr, "You must specify iscsi target portal.\n"); + print_usage(); + exit(10); + } + + iscsi = iscsi_create_context(initiator); + if (iscsi == NULL) { + printf("Failed to create context\n"); + exit(10); + } + + if (debug > 0) { + iscsi_set_log_level(iscsi, debug); + iscsi_set_log_fn(iscsi, iscsi_log_to_stderr); + } + + iscsi_url = iscsi_parse_full_url(iscsi, url); + + if (url) { + free(discard_const(url)); + } + + if (iscsi_url == NULL) { + fprintf(stderr, "Failed to parse URL: %s\n", + iscsi_get_error(iscsi)); + exit(10); + } + + iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL); + printf("Enable Header Digest\n"); + iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_CRC32C); + + printf("Disable iscsi reconnect on session failure\n"); + iscsi_set_noautoreconnect(iscsi, 1); + + state.lun = iscsi_url->lun; + if (iscsi_full_connect_sync(iscsi, iscsi_url->portal, iscsi_url->lun) + != 0) { + fprintf(stderr, "iscsi_connect failed. %s\n", + iscsi_get_error(iscsi)); + exit(10); + } + + printf("Verify that the connection works\n"); + if (iscsi_testunitready_task(iscsi, state.lun, + tur_cb, &state) == NULL) { + fprintf(stderr, "testunitready failed\n"); + exit(10); + } + event_loop(iscsi, &state, 3); + + iscsi_destroy_url(iscsi_url); + iscsi_disconnect(iscsi); + iscsi_destroy_context(iscsi); + + if (state.status != 0) { + exit(10); + } + return 0; +} + diff --git a/tests/test_0500_header_digest.sh b/tests/test_0500_header_digest.sh new file mode 100755 index 0000000..d67cbd3 --- /dev/null +++ b/tests/test_0500_header_digest.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +. ./functions.sh + +echo "Header Digest tests" + +start_target "nop_interval=1,nop_count=3" +enable_header_digest +create_lun + +echo -n "Test that we can connect to a target requiring Header Digest ..." +./prog_header_digest -i ${IQNINITIATOR} iscsi://${TGTPORTAL}/${IQNTARGET}/1 > /dev/null || failure +success + +shutdown_target +delete_lun + +exit 0