diff --git a/include/iscsi-private.h b/include/iscsi-private.h index bbc0462..010eeb8 100644 --- a/include/iscsi-private.h +++ b/include/iscsi-private.h @@ -65,6 +65,7 @@ struct iscsi_context { const char *initiator_name; const char *target_name; const char *target_address; /* If a redirect */ + const char *connected_portal; const char *alias; const char *user; @@ -83,6 +84,8 @@ struct iscsi_context { int fd; int is_connected; + + int tcp_user_timeout; int current_phase; int next_phase; @@ -120,6 +123,7 @@ struct iscsi_context { const char *portal; int no_auto_reconnect; int reconnect_deferred; + int debug; }; #define ISCSI_PDU_IMMEDIATE 0x40 diff --git a/include/iscsi.h b/include/iscsi.h index ea00f31..70881f6 100644 --- a/include/iscsi.h +++ b/include/iscsi.h @@ -231,6 +231,7 @@ enum scsi_status { SCSI_STATUS_GOOD = 0, SCSI_STATUS_CHECK_CONDITION = 2, SCSI_STATUS_RESERVATION_CONFLICT = 0x18, + SCSI_STATUS_REDIRECT = 0x101, SCSI_STATUS_CANCELLED = 0x0f000000, SCSI_STATUS_ERROR = 0x0f000001 }; @@ -958,6 +959,21 @@ iscsi_scsi_task_cancel(struct iscsi_context *iscsi, EXTERN void iscsi_scsi_cancel_all_tasks(struct iscsi_context *iscsi); +#define DPRINTF(iscsi,level,fmt,args...) do { if ((iscsi)->debug >= level) {fprintf(stderr,"libiscsi: ");fprintf(stderr, (fmt), ##args); fprintf(stderr,"\n");} } while (0); + +/* + * This function is to set the debugging level (0=disabled). + */ +EXTERN void +iscsi_set_debug(struct iscsi_context *iscsi, int level); + +/* + * This function is to set the TCP_USER_TIMEOUT option. It has to be called after iscsi + * context creation. The value given in ms is then applied each time a new socket is created. + */ +EXTERN void +iscsi_set_tcp_user_timeout(struct iscsi_context *iscsi, int timeout_ms); + #ifdef __cplusplus } #endif diff --git a/lib/connect.c b/lib/connect.c index 931f571..f82df28 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -35,6 +35,10 @@ struct connect_task { int lun; }; +static void +iscsi_connect_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, + void *private_data); + static void iscsi_testunitready_cb(struct iscsi_context *iscsi, int status, void *command_data, void *private_data) @@ -82,6 +86,14 @@ iscsi_login_cb(struct iscsi_context *iscsi, int status, void *command_data _U_, { struct connect_task *ct = private_data; + if (status == 0x101 && iscsi->target_address) { + iscsi_disconnect(iscsi); + if (iscsi_connect_async(iscsi, iscsi->target_address, iscsi_connect_cb, iscsi->connect_data) != 0) { + return; + } + return; + } + if (status != 0) { ct->cb(iscsi, SCSI_STATUS_ERROR, NULL, ct->private_data); return; @@ -158,6 +170,8 @@ int iscsi_reconnect(struct iscsi_context *old_iscsi) { struct iscsi_context *iscsi = old_iscsi; + DPRINTF(iscsi,2,"reconnect initiated"); + /* This is mainly for tests, where we do not want to automatically reconnect but rather want the commands to fail with an error if the target drops the session. @@ -195,6 +209,9 @@ int iscsi_reconnect(struct iscsi_context *old_iscsi) return 0; } + int retry = 0; + srand (time(NULL)^getpid()); + try_again: iscsi = iscsi_create_context(old_iscsi->initiator_name); @@ -213,10 +230,24 @@ try_again: iscsi->lun = old_iscsi->lun; iscsi->portal = strdup(old_iscsi->portal); + + iscsi->debug = old_iscsi->debug; + + iscsi->tcp_user_timeout = old_iscsi->tcp_user_timeout; if (iscsi_full_connect_sync(iscsi, iscsi->portal, iscsi->lun) != 0) { iscsi_destroy_context(iscsi); - sleep(1); + int backoff=retry; + if (backoff > 10) { + backoff+=rand()%10; + backoff-=5; + } + if (backoff > 30) { + backoff=30; + } + DPRINTF(iscsi,1,"reconnect try %d failed, waiting %d seconds",retry,backoff); + sleep(backoff); + retry++; goto try_again; } diff --git a/lib/init.c b/lib/init.c index 1c11bcf..9c6bcac 100644 --- a/lib/init.c +++ b/lib/init.c @@ -244,6 +244,11 @@ iscsi_destroy_context(struct iscsi_context *iscsi) free(discard_const(iscsi->chap_c)); iscsi->chap_c = NULL; + if (iscsi->connected_portal != NULL) { + free(discard_const(iscsi->connected_portal)); + iscsi->connected_portal = NULL; + } + iscsi->connect_data = NULL; free(iscsi); @@ -251,8 +256,6 @@ iscsi_destroy_context(struct iscsi_context *iscsi) return 0; } - - void iscsi_set_error(struct iscsi_context *iscsi, const char *error_string, ...) { @@ -270,9 +273,18 @@ iscsi_set_error(struct iscsi_context *iscsi, const char *error_string, ...) free(iscsi->error_string); iscsi->error_string = str; + va_end(ap); + + DPRINTF(iscsi,1,str); } +void +iscsi_set_debug(struct iscsi_context *iscsi, int level) +{ + iscsi->debug = level; + DPRINTF(iscsi,2,"set debug level to %d",level); +} const char * iscsi_get_error(struct iscsi_context *iscsi) diff --git a/lib/libiscsi.def b/lib/libiscsi.def index 865a62f..56ca61a 100644 --- a/lib/libiscsi.def +++ b/lib/libiscsi.def @@ -62,6 +62,7 @@ iscsi_scsi_command_sync iscsi_scsi_task_cancel iscsi_service iscsi_set_alias +iscsi_set_debug iscsi_set_header_digest iscsi_set_initiator_username_pwd iscsi_set_isid_en @@ -71,6 +72,7 @@ iscsi_set_isid_reserved iscsi_set_session_type iscsi_set_targetname iscsi_set_tcp_keepalive +iscsi_set_tcp_user_timeout iscsi_startstopunit_sync iscsi_startstopunit_task iscsi_synchronizecache10_sync diff --git a/lib/libiscsi.syms b/lib/libiscsi.syms index 9c6bc3a..dba697a 100644 --- a/lib/libiscsi.syms +++ b/lib/libiscsi.syms @@ -60,6 +60,7 @@ iscsi_scsi_command_sync iscsi_scsi_task_cancel iscsi_service iscsi_set_alias +iscsi_set_debug iscsi_set_header_digest iscsi_set_initiator_username_pwd iscsi_set_isid_en @@ -69,6 +70,7 @@ iscsi_set_isid_reserved iscsi_set_session_type iscsi_set_targetname iscsi_set_tcp_keepalive +iscsi_set_tcp_user_timeout iscsi_startstopunit_sync iscsi_startstopunit_task iscsi_synchronizecache10_sync diff --git a/lib/login.c b/lib/login.c index b685743..d841a4b 100644 --- a/lib/login.c +++ b/lib/login.c @@ -1088,6 +1088,13 @@ iscsi_process_login_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, size -= len + 1; } + if (status == 0x101 && iscsi->target_address) { + DPRINTF(iscsi,2,"target requests redirect to %s",iscsi->target_address); + pdu->callback(iscsi, SCSI_STATUS_REDIRECT, NULL, + pdu->private_data); + return 0; + } + if (status != 0) { iscsi_set_error(iscsi, "Failed to log in to target. Status: %s(%d)", login_error_str(status), status); diff --git a/lib/scsi-command.c b/lib/scsi-command.c index 7c6770e..3122b52 100644 --- a/lib/scsi-command.c +++ b/lib/scsi-command.c @@ -242,8 +242,8 @@ iscsi_scsi_command_async(struct iscsi_context *iscsi, int lun, if (iscsi->use_immediate_data == ISCSI_IMMEDIATE_DATA_YES) { uint32_t len = data.size; - if (len > iscsi->target_max_recv_data_segment_length) { - len = iscsi->target_max_recv_data_segment_length; + if (len > iscsi->first_burst_length) { + len = iscsi->first_burst_length; } if (iscsi_pdu_add_data(iscsi, pdu, data.data, len) @@ -302,7 +302,7 @@ iscsi_scsi_command_async(struct iscsi_context *iscsi, int lun, } /* Can we send some unsolicited data ? */ - if (pdu->nidata.size != 0 && iscsi->use_initial_r2t == ISCSI_INITIAL_R2T_NO) { + if (pdu->nidata.size != 0 && iscsi->use_initial_r2t == ISCSI_INITIAL_R2T_NO && iscsi->use_immediate_data == ISCSI_IMMEDIATE_DATA_NO) { uint32_t len = pdu->nidata.size - offset; if (len > iscsi->first_burst_length) { diff --git a/lib/socket.c b/lib/socket.c index 8ec437a..9912976 100644 --- a/lib/socket.c +++ b/lib/socket.c @@ -68,6 +68,8 @@ iscsi_connect_async(struct iscsi_context *iscsi, const char *portal, struct addrinfo *ai = NULL; int socksize; + DPRINTF(iscsi,2,"connecting to portal %s",portal); + if (iscsi->fd != -1) { iscsi_set_error(iscsi, "Trying to connect but already connected."); @@ -157,6 +159,12 @@ iscsi_connect_async(struct iscsi_context *iscsi, const char *portal, iscsi->connect_data = private_data; set_nonblocking(iscsi->fd); + + iscsi_set_tcp_keepalive(iscsi, 30, 3, 30); + + if (iscsi->tcp_user_timeout > 0) { + set_tcp_user_timeout(iscsi); + } if (connect(iscsi->fd, ai->ai_addr, socksize) != 0 && errno != EINPROGRESS) { @@ -169,6 +177,10 @@ iscsi_connect_async(struct iscsi_context *iscsi, const char *portal, } freeaddrinfo(ai); + + if (iscsi->connected_portal) free(iscsi->connected_portal); + iscsi->connected_portal=strdup(portal); + return 0; } @@ -182,6 +194,9 @@ iscsi_disconnect(struct iscsi_context *iscsi) } close(iscsi->fd); + + if (iscsi->connected_portal) + DPRINTF(iscsi,2,"disconnected from portal %s",iscsi->connected_portal); iscsi->fd = -1; iscsi->is_connected = 0; @@ -437,7 +452,6 @@ iscsi_service(struct iscsi_context *iscsi, int revents) return -1; } - iscsi_set_tcp_keepalive(iscsi, 30, 3, 30); iscsi->is_connected = 1; iscsi->socket_status_cb(iscsi, SCSI_STATUS_GOOD, NULL, iscsi->connect_data); @@ -515,6 +529,40 @@ iscsi_free_iscsi_inqueue(struct iscsi_in_pdu *inqueue) } } +#ifndef TCP_USER_TIMEOUT +#define TCP_USER_TIMEOUT 18 +#endif + +int set_tcp_user_timeout(struct iscsi_context *iscsi) +{ + int level, value; + + #if defined(__FreeBSD__) || defined(__sun) + struct protoent *buf; + + if ((buf = getprotobyname("tcp")) != NULL) + level = buf->p_proto; + else + return -1; + #else + level = SOL_TCP; + #endif + + value = iscsi->tcp_user_timeout; + if (setsockopt(iscsi->fd, level, TCP_USER_TIMEOUT, &value, sizeof(value)) != 0) { + iscsi_set_error(iscsi, "TCP: Failed to set tcp user timeout. Error %s(%d)", strerror(errno), errno); + return -1; + } + DPRINTF(iscsi,3,"TCP_USER_TIMEOUT set to %d",value); + return 0; +} + +void iscsi_set_tcp_user_timeout(struct iscsi_context *iscsi, int timeout_ms) +{ + iscsi->tcp_user_timeout=timeout_ms; + DPRINTF(iscsi,2,"TCP_USER_TIMEOUT will be set to %dms on next socket creation",timeout_ms); +} + int iscsi_set_tcp_keepalive(struct iscsi_context *iscsi, int idle, int count, int interval) { int level, value; @@ -535,6 +583,7 @@ int iscsi_set_tcp_keepalive(struct iscsi_context *iscsi, int idle, int count, in iscsi_set_error(iscsi, "TCP: Failed to set socket option SO_KEEPALIVE. Error %s(%d)", strerror(errno), errno); return -1; } + DPRINTF(iscsi,3,"SO_KEEPALIVE set to %d",value); #endif #ifdef TCP_KEEPCNT value = count; @@ -542,6 +591,7 @@ int iscsi_set_tcp_keepalive(struct iscsi_context *iscsi, int idle, int count, in iscsi_set_error(iscsi, "TCP: Failed to set tcp keepalive count. Error %s(%d)", strerror(errno), errno); return -1; } + DPRINTF(iscsi,3,"TCP_KEEPCNT set to %d",value); #endif #ifdef TCP_KEEPINTVL value = interval; @@ -549,6 +599,7 @@ int iscsi_set_tcp_keepalive(struct iscsi_context *iscsi, int idle, int count, in iscsi_set_error(iscsi, "TCP: Failed to set tcp keepalive interval. Error %s(%d)", strerror(errno), errno); return -1; } + DPRINTF(iscsi,3,"TCP_KEEPINTVL set to %d",value); #endif #ifdef TCP_KEEPIDLE value = idle; @@ -556,6 +607,7 @@ int iscsi_set_tcp_keepalive(struct iscsi_context *iscsi, int idle, int count, in iscsi_set_error(iscsi, "TCP: Failed to set tcp keepalive idle. Error %s(%d)", strerror(errno), errno); return -1; } + DPRINTF(iscsi,3,"TCP_KEEPIDLE set to %d",value); #endif return 0; diff --git a/src/iscsi-inq.c b/src/iscsi-inq.c index e0fe471..87b6d35 100644 --- a/src/iscsi-inq.c +++ b/src/iscsi-inq.c @@ -196,6 +196,7 @@ void print_help(void) fprintf(stderr, " -i, --initiator-name=iqn-name Initiatorname to use\n"); fprintf(stderr, " -e, --evpd=integer evpd\n"); fprintf(stderr, " -c, --pagecode=integer page code\n"); + fprintf(stderr, " -d, --debug=integer debug level (0=disabled)\n"); fprintf(stderr, "\n"); fprintf(stderr, "Help options:\n"); fprintf(stderr, " -?, --help Show this help message\n"); @@ -218,7 +219,7 @@ int main(int argc, const char *argv[]) const char *url = NULL; struct iscsi_url *iscsi_url = NULL; int evpd = 0, pagecode = 0; - int show_help = 0, show_usage = 0; + int show_help = 0, show_usage = 0, debug = 0; int res; struct poptOption popt_options[] = { @@ -227,6 +228,7 @@ int main(int argc, const char *argv[]) { "initiator-name", 'i', POPT_ARG_STRING, &initiator, 0, "Initiatorname to use", "iqn-name" }, { "evpd", 'e', POPT_ARG_INT, &evpd, 0, "evpd", "integer" }, { "pagecode", 'c', POPT_ARG_INT, &pagecode, 0, "page code", "integer" }, + { "debug", 'd', POPT_ARG_INT, &debug, 0, "Debugging level", "integer" }, POPT_TABLEEND }; @@ -263,6 +265,10 @@ int main(int argc, const char *argv[]) exit(10); } + if (debug > 0) { + iscsi_set_debug(iscsi, debug); + } + if (url == NULL) { fprintf(stderr, "You must specify the URL\n"); print_usage(); diff --git a/src/iscsi-ls.c b/src/iscsi-ls.c index 1ce5b3b..6951a57 100644 --- a/src/iscsi-ls.c +++ b/src/iscsi-ls.c @@ -291,6 +291,7 @@ void print_help(void) fprintf(stderr, "Usage: iscsi-ls [OPTION...] \n"); fprintf(stderr, " -i, --initiator-name=iqn-name Initiatorname to use\n"); fprintf(stderr, " -s, --show-luns Show the luns for each target\n"); + fprintf(stderr, " -d, --debug=integer debug level (0=disabled)\n"); fprintf(stderr, "\n"); fprintf(stderr, "Help options:\n"); fprintf(stderr, " -?, --help Show this help message\n"); @@ -314,13 +315,14 @@ int main(int argc, const char *argv[]) const char *url = NULL; poptContext pc; int res; - int show_help = 0, show_usage = 0; + int show_help = 0, show_usage = 0, debug = 0; struct poptOption popt_options[] = { { "help", '?', POPT_ARG_NONE, &show_help, 0, "Show this help message", NULL }, { "usage", 0, POPT_ARG_NONE, &show_usage, 0, "Display brief usage message", NULL }, { "initiator-name", 'i', POPT_ARG_STRING, &initiator, 0, "Initiatorname to use", "iqn-name" }, { "show-luns", 's', POPT_ARG_NONE, &showluns, 0, "Show the luns for each target", NULL }, + { "debug", 'd', POPT_ARG_INT, &debug, 0, "Debugging level", "integer" }, POPT_TABLEEND }; @@ -353,7 +355,6 @@ int main(int argc, const char *argv[]) poptFreeContext(pc); - if (url == NULL) { fprintf(stderr, "You must specify iscsi target portal.\n"); print_usage(); @@ -366,6 +367,10 @@ int main(int argc, const char *argv[]) exit(10); } + if (debug > 0) { + iscsi_set_debug(iscsi, debug); + } + iscsi_url = iscsi_parse_portal_url(iscsi, url); if (iscsi_url == NULL) { fprintf(stderr, "Failed to parse URL: %s\n",