diff --git a/include/iscsi-private.h b/include/iscsi-private.h index 93d98fc..e69fdce 100644 --- a/include/iscsi-private.h +++ b/include/iscsi-private.h @@ -80,6 +80,7 @@ struct iscsi_context { int fd; int is_connected; + int is_corked; int tcp_user_timeout; int tcp_keepcnt; @@ -125,6 +126,7 @@ struct iscsi_context { int no_auto_reconnect; int reconnect_deferred; int reconnect_max_retries; + int pending_reconnect; int log_level; iscsi_log_fn log_fn; @@ -188,8 +190,9 @@ enum iscsi_opcode { ISCSI_PDU_DATA_IN = 0x25, ISCSI_PDU_LOGOUT_RESPONSE = 0x26, ISCSI_PDU_R2T = 0x31, + ISCSI_PDU_ASYNC_MSG = 0x32, ISCSI_PDU_REJECT = 0x3f, - ISCSI_PDU_NO_PDU = 0xff + ISCSI_PDU_NO_PDU = 0xff }; struct iscsi_scsi_cbdata { @@ -209,6 +212,13 @@ struct iscsi_pdu { * This includes any DATA-OUT PDU as well as all NOPs. */ #define ISCSI_PDU_DROP_ON_RECONNECT 0x00000004 +/* stop sending after this PDU has been sent */ +#define ISCSI_PDU_CORK_WHEN_SENT 0x00000008 +/* put this immediate delivery PDU in front of outqueue. + * This is currently only used for immediate logout requests + * as answer to an async logout event. */ +#define ISCSI_PDU_URGENT_DELIVERY 0x00000010 + uint32_t flags; @@ -339,14 +349,18 @@ iscsi_log_message(struct iscsi_context *iscsi, int level, const char *format, .. void iscsi_add_to_outqueue(struct iscsi_context *iscsi, struct iscsi_pdu *pdu); -int -iscsi_serial32_compare(uint32_t s1, uint32_t s2); +int iscsi_serial32_compare(uint32_t s1, uint32_t s2); +void iscsi_adjust_statsn(struct iscsi_context *iscsi, struct iscsi_in_pdu *in); +void iscsi_adjust_maxexpcmdsn(struct iscsi_context *iscsi, struct iscsi_in_pdu *in); -uint32_t -iscsi_itt_post_increment(struct iscsi_context *iscsi); +uint32_t iscsi_itt_post_increment(struct iscsi_context *iscsi); void iscsi_timeout_scan(struct iscsi_context *iscsi); +int +iscsi_logout_async_internal(struct iscsi_context *iscsi, iscsi_command_cb cb, + void *private_data, uint32_t flags); + #ifdef __cplusplus } #endif diff --git a/lib/iscsi-command.c b/lib/iscsi-command.c index 95187a1..6a7bec7 100644 --- a/lib/iscsi-command.c +++ b/lib/iscsi-command.c @@ -208,7 +208,7 @@ iscsi_scsi_command_async(struct iscsi_context *iscsi, int lun, return -1; } - if (iscsi->is_loggedin == 0) { + if (iscsi->is_loggedin == 0 && !iscsi->pending_reconnect) { iscsi_set_error(iscsi, "Trying to send command while " "not logged in."); return -1; @@ -347,23 +347,12 @@ int iscsi_process_scsi_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, struct iscsi_in_pdu *in) { - uint32_t statsn, maxcmdsn, expcmdsn, flags, status; + uint32_t flags, status; struct iscsi_scsi_cbdata *scsi_cbdata = &pdu->scsi_cbdata; struct scsi_task *task = scsi_cbdata->task; - statsn = scsi_get_uint32(&in->hdr[24]); - if (statsn > iscsi->statsn) { - iscsi->statsn = statsn; - } - - maxcmdsn = scsi_get_uint32(&in->hdr[32]); - if (iscsi_serial32_compare(maxcmdsn, iscsi->maxcmdsn) > 0) { - iscsi->maxcmdsn = maxcmdsn; - } - expcmdsn = scsi_get_uint32(&in->hdr[28]); - if (iscsi_serial32_compare(expcmdsn, iscsi->expcmdsn) > 0) { - iscsi->expcmdsn = expcmdsn; - } + iscsi_adjust_statsn(iscsi, in); + iscsi_adjust_maxexpcmdsn(iscsi, in); flags = in->hdr[1]; if ((flags&ISCSI_PDU_DATA_FINAL) == 0) { @@ -489,24 +478,13 @@ int iscsi_process_scsi_data_in(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, struct iscsi_in_pdu *in, int *is_finished) { - uint32_t statsn, maxcmdsn, expcmdsn, flags, status; + uint32_t flags, status; struct iscsi_scsi_cbdata *scsi_cbdata = &pdu->scsi_cbdata; struct scsi_task *task = scsi_cbdata->task; int dsl; - statsn = scsi_get_uint32(&in->hdr[24]); - if (statsn > iscsi->statsn) { - iscsi->statsn = statsn; - } - - maxcmdsn = scsi_get_uint32(&in->hdr[32]); - if (iscsi_serial32_compare(maxcmdsn, iscsi->maxcmdsn) > 0) { - iscsi->maxcmdsn = maxcmdsn; - } - expcmdsn = scsi_get_uint32(&in->hdr[28]); - if (iscsi_serial32_compare(expcmdsn, iscsi->expcmdsn) > 0) { - iscsi->expcmdsn = expcmdsn; - } + iscsi_adjust_statsn(iscsi, in); + iscsi_adjust_maxexpcmdsn(iscsi, in); flags = in->hdr[1]; if ((flags&ISCSI_PDU_DATA_ACK_REQUESTED) != 0) { @@ -579,20 +557,13 @@ int iscsi_process_r2t(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, struct iscsi_in_pdu *in) { - uint32_t ttt, offset, len, maxcmdsn, expcmdsn; + uint32_t ttt, offset, len; ttt = scsi_get_uint32(&in->hdr[20]); offset = scsi_get_uint32(&in->hdr[40]); len = scsi_get_uint32(&in->hdr[44]); - maxcmdsn = scsi_get_uint32(&in->hdr[32]); - if (iscsi_serial32_compare(maxcmdsn, iscsi->maxcmdsn) > 0) { - iscsi->maxcmdsn = maxcmdsn; - } - expcmdsn = scsi_get_uint32(&in->hdr[28]); - if (iscsi_serial32_compare(expcmdsn, iscsi->expcmdsn) > 0) { - iscsi->expcmdsn = expcmdsn; - } + iscsi_adjust_maxexpcmdsn(iscsi, in); pdu->datasn = 0; iscsi_send_data_out(iscsi, pdu, ttt, offset, len); diff --git a/lib/login.c b/lib/login.c index 32c3c8c..59c55d2 100644 --- a/lib/login.c +++ b/lib/login.c @@ -965,22 +965,14 @@ int iscsi_process_login_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, struct iscsi_in_pdu *in) { - uint32_t status, maxcmdsn, expcmdsn; + uint32_t status; char *ptr = (char *)in->data; int size = in->data_pos; status = scsi_get_uint16(&in->hdr[36]); - iscsi->statsn = scsi_get_uint16(&in->hdr[24]); - - maxcmdsn = scsi_get_uint32(&in->hdr[32]); - if (iscsi_serial32_compare(maxcmdsn, iscsi->maxcmdsn) > 0) { - iscsi->maxcmdsn = maxcmdsn; - } - expcmdsn = scsi_get_uint32(&in->hdr[28]); - if (iscsi_serial32_compare(expcmdsn, iscsi->expcmdsn) > 0) { - iscsi->expcmdsn = expcmdsn; - } + iscsi_adjust_statsn(iscsi, in); + iscsi_adjust_maxexpcmdsn(iscsi, in); /* XXX here we should parse the data returned in case the target * renegotiated some some parameters. @@ -1117,10 +1109,9 @@ iscsi_process_login_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, return 0; } - int -iscsi_logout_async(struct iscsi_context *iscsi, iscsi_command_cb cb, - void *private_data) +iscsi_logout_async_internal(struct iscsi_context *iscsi, iscsi_command_cb cb, + void *private_data, uint32_t flags) { struct iscsi_pdu *pdu; @@ -1154,6 +1145,7 @@ iscsi_logout_async(struct iscsi_context *iscsi, iscsi_command_cb cb, pdu->callback = cb; pdu->private_data = private_data; + pdu->flags |= ISCSI_PDU_CORK_WHEN_SENT | flags; if (iscsi_queue_pdu(iscsi, pdu) != 0) { iscsi_set_error(iscsi, "Out-of-memory: failed to queue iscsi " @@ -1165,20 +1157,18 @@ iscsi_logout_async(struct iscsi_context *iscsi, iscsi_command_cb cb, return 0; } +int +iscsi_logout_async(struct iscsi_context *iscsi, iscsi_command_cb cb, + void *private_data) +{ + return iscsi_logout_async_internal(iscsi, cb, private_data, 0); +} + int iscsi_process_logout_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, struct iscsi_in_pdu *in) { - uint32_t maxcmdsn, expcmdsn; - - maxcmdsn = scsi_get_uint32(&in->hdr[32]); - if (iscsi_serial32_compare(maxcmdsn, iscsi->maxcmdsn) > 0) { - iscsi->maxcmdsn = maxcmdsn; - } - expcmdsn = scsi_get_uint32(&in->hdr[28]); - if (iscsi_serial32_compare(expcmdsn, iscsi->expcmdsn) > 0) { - iscsi->expcmdsn = expcmdsn; - } + iscsi_adjust_maxexpcmdsn(iscsi, in); iscsi->is_loggedin = 0; pdu->callback(iscsi, SCSI_STATUS_GOOD, NULL, pdu->private_data); diff --git a/lib/pdu.c b/lib/pdu.c index 89b15c2..66a24e8 100644 --- a/lib/pdu.c +++ b/lib/pdu.c @@ -69,6 +69,31 @@ iscsi_itt_post_increment(struct iscsi_context *iscsi) { return old_itt; } +void iscsi_adjust_statsn(struct iscsi_context *iscsi, struct iscsi_in_pdu *in) { + uint32_t statsn = scsi_get_uint32(&in->hdr[24]); + uint32_t itt = scsi_get_uint32(&in->hdr[16]); + + if (itt == 0xffffffff) { + /* target will not increase statsn if itt == 0xffffffff */ + statsn--; + } + + if (iscsi_serial32_compare(statsn, iscsi->statsn) > 0) { + iscsi->statsn = statsn; + } +} + +void iscsi_adjust_maxexpcmdsn(struct iscsi_context *iscsi, struct iscsi_in_pdu *in) { + uint32_t maxcmdsn = scsi_get_uint32(&in->hdr[32]); + if (iscsi_serial32_compare(maxcmdsn, iscsi->maxcmdsn) > 0) { + iscsi->maxcmdsn = maxcmdsn; + } + uint32_t expcmdsn = scsi_get_uint32(&in->hdr[28]); + if (iscsi_serial32_compare(expcmdsn, iscsi->expcmdsn) > 0) { + iscsi->expcmdsn = expcmdsn; + } +} + void iscsi_dump_pdu_header(struct iscsi_context *iscsi, unsigned char *data) { char dump[ISCSI_RAW_HEADER_SIZE*3+1]={0}; int i; @@ -147,6 +172,10 @@ iscsi_free_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu) } pdu->indata.data = NULL; + if (iscsi->outqueue_current == pdu) { + iscsi->outqueue_current = NULL; + } + iscsi_sfree(iscsi, pdu); } @@ -297,14 +326,10 @@ int iscsi_process_target_nop_in(struct iscsi_context *iscsi, struct iscsi_in_pdu *in) { uint32_t ttt; - uint32_t statsn; ttt = scsi_get_uint32(&in->hdr[20]); - statsn = scsi_get_uint32(&in->hdr[24]); - if (statsn > iscsi->statsn) { - iscsi->statsn = statsn; - } + iscsi_adjust_statsn(iscsi, in); /* if the server does not want a response */ if (ttt == 0xffffffff) { @@ -317,12 +342,25 @@ int iscsi_process_target_nop_in(struct iscsi_context *iscsi, } +static void iscsi_reconnect_after_logout(struct iscsi_context *iscsi, int status, + void *command_data _U_, void *opaque _U_) +{ + if (status) { + ISCSI_LOG(iscsi, 1, "logout failed: %s", iscsi_get_error(iscsi)); + } else { + ISCSI_LOG(iscsi, 2, "logout was successful"); + } + iscsi->pending_reconnect = 1; +} + + int iscsi_process_reject(struct iscsi_context *iscsi, struct iscsi_in_pdu *in) { int size = in->data_pos; uint32_t itt; struct iscsi_pdu *pdu; + uint8_t reason = in->hdr[2]; if (size < ISCSI_RAW_HEADER_SIZE) { iscsi_set_error(iscsi, "size of REJECT payload is too small." @@ -331,6 +369,14 @@ int iscsi_process_reject(struct iscsi_context *iscsi, return -1; } + if (reason == ISCSI_REJECT_WAITING_FOR_LOGOUT) { + ISCSI_LOG(iscsi, 1, "target rejects request with reason: %s", iscsi_reject_reason_str(reason)); + iscsi_logout_async_internal(iscsi, iscsi_reconnect_after_logout, NULL, ISCSI_PDU_DROP_ON_RECONNECT|ISCSI_PDU_URGENT_DELIVERY); + return 0; + } + + iscsi_set_error(iscsi, "Request was rejected with reason: 0x%02x (%s)", reason, iscsi_reject_reason_str(reason)); + itt = scsi_get_uint32(&in->data[16]); if (iscsi->log_level > 1) { @@ -350,8 +396,10 @@ int iscsi_process_reject(struct iscsi_context *iscsi, return -1; } - pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, - pdu->private_data); + if (pdu->callback) { + pdu->callback(iscsi, SCSI_STATUS_ERROR, NULL, + pdu->private_data); + } ISCSI_LIST_REMOVE(&iscsi->waitpdu, pdu); iscsi_free_pdu(iscsi, pdu); @@ -376,13 +424,36 @@ iscsi_process_pdu(struct iscsi_context *iscsi, struct iscsi_in_pdu *in) return -1; } - if (opcode == ISCSI_PDU_REJECT) { - iscsi_set_error(iscsi, "Request was rejected with reason: 0x%02x (%s)", in->hdr[2], iscsi_reject_reason_str(in->hdr[2])); - - if (iscsi_process_reject(iscsi, in) != 0) { + if (opcode == ISCSI_PDU_ASYNC_MSG) { + uint8_t event = in->hdr[36]; + uint16_t param1 = scsi_get_uint16(&in->hdr[38]); + uint16_t param2 = scsi_get_uint16(&in->hdr[40]); + uint16_t param3 = scsi_get_uint16(&in->hdr[42]); + switch (event) { + case 0x1: + ISCSI_LOG(iscsi, 2, "target requests logout within %u seconds", param3); + iscsi_logout_async_internal(iscsi, iscsi_reconnect_after_logout, NULL, ISCSI_PDU_DROP_ON_RECONNECT|ISCSI_PDU_URGENT_DELIVERY); + return 0; + case 0x2: + ISCSI_LOG(iscsi, 2, "target will drop this connection. Time2Wait is %u seconds", param2); + iscsi->last_reconnect = time(NULL) + param2; + return 0; + case 0x3: + ISCSI_LOG(iscsi, 2, "target will drop all connections of this session. Time2Wait is %u seconds", param2); + iscsi->last_reconnect = time(NULL) + param2; + return 0; + case 0x4: + ISCSI_LOG(iscsi, 2, "target requests parameter renogitiation."); + iscsi_logout_async_internal(iscsi, iscsi_reconnect_after_logout, NULL, ISCSI_PDU_DROP_ON_RECONNECT); + return 0; + default: + ISCSI_LOG(iscsi, 1, "unhandled async event %u: param1 %u param2 %u param3 %u", event, param1, param2, param3); return -1; } - return 0; + } + + if (opcode == ISCSI_PDU_REJECT) { + return iscsi_process_reject(iscsi, in); } if (opcode == ISCSI_PDU_NOP_IN && itt == 0xffffffff) { diff --git a/lib/socket.c b/lib/socket.c index 4b1c569..9844084 100644 --- a/lib/socket.c +++ b/lib/socket.c @@ -94,7 +94,8 @@ iscsi_add_to_outqueue(struct iscsi_context *iscsi, struct iscsi_pdu *pdu) * ensure that pakets with the same CmdSN are kept in FIFO order. */ do { - if (iscsi_serial32_compare(pdu->cmdsn, current->cmdsn) < 0) { + if (iscsi_serial32_compare(pdu->cmdsn, current->cmdsn) < 0 || + pdu->flags & ISCSI_PDU_URGENT_DELIVERY) { /* insert PDU before the current */ if (last != NULL) { last->next=pdu; @@ -611,8 +612,15 @@ iscsi_write_to_socket(struct iscsi_context *iscsi) while (iscsi->outqueue != NULL || iscsi->outqueue_current != NULL) { if (iscsi->outqueue_current == NULL) { - if (iscsi_serial32_compare(iscsi->outqueue->cmdsn, iscsi->maxcmdsn) > 0) { - /* stop sending. maxcmdsn is reached */ + if (iscsi->is_corked) { + /* connection is corked we are not allowed to send + * additional PDUs */ + return 0; + } + + if (iscsi_serial32_compare(iscsi->outqueue->cmdsn, iscsi->maxcmdsn) > 0 + && !(iscsi->outqueue->outdata.data[0] & ISCSI_PDU_IMMEDIATE)) { + /* stop sending for non-immediate PDUs. maxcmdsn is reached */ return 0; } /* pop first element of the outqueue */ @@ -698,10 +706,12 @@ iscsi_write_to_socket(struct iscsi_context *iscsi) if (pdu->payload_written != total) { return 0; } - if (pdu->flags & ISCSI_PDU_DELETE_WHEN_SENT) { iscsi_free_pdu(iscsi, pdu); } + if (pdu->flags & ISCSI_PDU_CORK_WHEN_SENT) { + iscsi->is_corked = 1; + } iscsi->outqueue_current = NULL; } return 0; @@ -725,6 +735,10 @@ iscsi_service(struct iscsi_context *iscsi, int revents) return 0; } + if (iscsi->pending_reconnect) { + iscsi_reconnect(iscsi); + } + if (revents & POLLERR) { int err = 0; socklen_t err_size = sizeof(err); diff --git a/lib/task_mgmt.c b/lib/task_mgmt.c index 68889a4..7a3d749 100644 --- a/lib/task_mgmt.c +++ b/lib/task_mgmt.c @@ -92,18 +92,11 @@ int iscsi_process_task_mgmt_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, struct iscsi_in_pdu *in) { - uint32_t response, maxcmdsn, expcmdsn; + uint32_t response; response = in->hdr[2]; - maxcmdsn = scsi_get_uint32(&in->hdr[32]); - if (iscsi_serial32_compare(maxcmdsn, iscsi->maxcmdsn) > 0) { - iscsi->maxcmdsn = maxcmdsn; - } - expcmdsn = scsi_get_uint32(&in->hdr[28]); - if (iscsi_serial32_compare(expcmdsn, iscsi->expcmdsn) > 0) { - iscsi->expcmdsn = expcmdsn; - } + iscsi_adjust_maxexpcmdsn(iscsi, in); pdu->callback(iscsi, SCSI_STATUS_GOOD, &response, pdu->private_data);