/* Copyright (C) 2010 by Ronnie Sahlberg This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_ARPA_INET_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_POLL_H #include #endif #ifdef AROS #include "aros/aros_compat.h" #endif #ifdef HAVE_SYS_SOCKET_H #include #endif #if defined(_WIN32) #include #include #include "win32/win32_compat.h" #else #include #include #include #include #include #endif #ifdef NEED_SYS_FILIO_H #include #endif #ifdef HAVE_SYS_UIO_H #include #endif #include #include #include #include #include #include #include #include "scsi-lowlevel.h" #include "iscsi.h" #include "iscsi-private.h" #include "slist.h" static uint32_t iface_rr = 0; struct iscsi_transport; /* MUST keep in sync with iser.c */ union socket_address { struct sockaddr_in sin; struct sockaddr_in6 sin6; struct sockaddr sa; }; void iscsi_add_to_outqueue(struct iscsi_context *iscsi, struct iscsi_pdu *pdu) { struct iscsi_pdu *current = iscsi->outqueue; struct iscsi_pdu *last = NULL; if (iscsi->scsi_timeout > 0) { pdu->scsi_timeout = time(NULL) + iscsi->scsi_timeout; } else { pdu->scsi_timeout = 0; } if (iscsi->outqueue == NULL) { iscsi->outqueue = pdu; pdu->next = NULL; return; } /* queue pdus in ascending order of CmdSN. * ensure that pakets with the same CmdSN are kept in FIFO order. * immediate PDUs are queued in front of queue with the CmdSN * of the first element in the outqueue. */ if (pdu->outdata.data[0] & ISCSI_PDU_IMMEDIATE) { iscsi_pdu_set_cmdsn(pdu, current->cmdsn); } do { if (iscsi_serial32_compare(pdu->cmdsn, current->cmdsn) < 0 || (pdu->outdata.data[0] & ISCSI_PDU_IMMEDIATE && !(current->outdata.data[0] & ISCSI_PDU_IMMEDIATE))) { /* insert PDU before the current */ if (last != NULL) { last->next=pdu; } else { iscsi->outqueue=pdu; } pdu->next = current; return; } last=current; current=current->next; } while (current != NULL); last->next = pdu; pdu->next = NULL; } void iscsi_decrement_iface_rr() { iface_rr--; } static int set_nonblocking(int fd) { #if defined(_WIN32) unsigned long opt = 1; return ioctlsocket(fd, FIONBIO, &opt); #else unsigned v; v = fcntl(fd, F_GETFL, 0); return fcntl(fd, F_SETFL, v | O_NONBLOCK); #endif } static int set_tcp_sockopt(int sockfd, int optname, int value) { int level; #ifndef SOL_TCP struct protoent *buf; if ((buf = getprotobyname("tcp")) != NULL) level = buf->p_proto; else return -1; #else level = SOL_TCP; #endif return setsockopt(sockfd, level, optname, (char *)&value, sizeof(value)); } #ifndef TCP_USER_TIMEOUT #define TCP_USER_TIMEOUT 18 #endif static int set_tcp_user_timeout(struct iscsi_context *iscsi) { if (set_tcp_sockopt(iscsi->fd, TCP_USER_TIMEOUT, iscsi->tcp_user_timeout) != 0) { iscsi_set_error(iscsi, "TCP: Failed to set tcp user timeout. Error %s(%d)", strerror(errno), errno); return -1; } ISCSI_LOG(iscsi, 3, "TCP_USER_TIMEOUT set to %d",iscsi->tcp_user_timeout); return 0; } #ifndef TCP_SYNCNT #define TCP_SYNCNT 7 #endif static int set_tcp_syncnt(struct iscsi_context *iscsi) { if (set_tcp_sockopt(iscsi->fd, TCP_SYNCNT, iscsi->tcp_syncnt) != 0) { iscsi_set_error(iscsi, "TCP: Failed to set tcp syn retries. Error %s(%d)", strerror(errno), errno); return -1; } ISCSI_LOG(iscsi, 3, "TCP_SYNCNT set to %d",iscsi->tcp_syncnt); return 0; } static int iscsi_tcp_connect(struct iscsi_context *iscsi, union socket_address *sa, int ai_family) { int socksize; switch (ai_family) { case AF_INET: socksize = sizeof(struct sockaddr_in); break; case AF_INET6: socksize = sizeof(struct sockaddr_in6); break; default: iscsi_set_error(iscsi, "Unknown address family :%d. " "Only IPv4/IPv6 supported so far.", ai_family); return -1; } iscsi->fd = socket(ai_family, SOCK_STREAM, 0); if (iscsi->fd == -1) { iscsi_set_error(iscsi, "Failed to open iscsi socket. " "Errno:%s(%d).", strerror(errno), errno); return -1; } if (iscsi->old_iscsi && iscsi->fd != iscsi->old_iscsi->fd) { if (dup2(iscsi->fd, iscsi->old_iscsi->fd) == -1) { return -1; } close(iscsi->fd); iscsi->fd = iscsi->old_iscsi->fd; } iscsi->tcp_nonblocking = !set_nonblocking(iscsi->fd); iscsi_set_tcp_keepalive(iscsi, iscsi->tcp_keepidle, iscsi->tcp_keepcnt, iscsi->tcp_keepintvl); if (iscsi->tcp_user_timeout > 0) { set_tcp_user_timeout(iscsi); } if (iscsi->tcp_syncnt > 0) { set_tcp_syncnt(iscsi); } #if __linux if (iscsi->bind_interfaces[0]) { char *pchr = iscsi->bind_interfaces, *pchr2; int iface_n = iface_rr++%iscsi->bind_interfaces_cnt; int iface_c = 0; do { pchr2 = strchr(pchr,','); if (iface_c == iface_n) { if (pchr2) pchr2[0]=0x00; break; } if (pchr2) {pchr=pchr2+1;} iface_c++; } while (pchr2); int res = setsockopt(iscsi->fd, SOL_SOCKET, SO_BINDTODEVICE, pchr, strlen(pchr)); if (res < 0) { ISCSI_LOG(iscsi,1,"failed to bind to interface '%s': %s",pchr,strerror(errno)); } else { ISCSI_LOG(iscsi,3,"successfully bound to interface '%s'",pchr); } if (pchr2) pchr2[0]=','; } #endif if (set_tcp_sockopt(iscsi->fd, TCP_NODELAY, 1) != 0) { ISCSI_LOG(iscsi,1,"failed to set TCP_NODELAY sockopt: %s",strerror(errno)); } else { ISCSI_LOG(iscsi,3,"TCP_NODELAY set to 1"); } if (connect(iscsi->fd, &sa->sa, socksize) != 0 #if defined(_WIN32) && WSAGetLastError() != WSAEWOULDBLOCK #endif && errno != EINPROGRESS) { iscsi_set_error(iscsi, "Connect failed with errno : " "%s(%d)", strerror(errno), errno); close(iscsi->fd); iscsi->fd = -1; return -1; } return 0; } int iscsi_connect_async(struct iscsi_context *iscsi, const char *portal, iscsi_command_cb cb, void *private_data) { int port = 3260; char *str; char *addr, *host; struct addrinfo *ai = NULL; union socket_address sa; int socksize; ISCSI_LOG(iscsi, 2, "connecting to portal %s",portal); if (iscsi->fd != -1) { iscsi_set_error(iscsi, "Trying to connect but already connected."); return -1; } addr = iscsi_strdup(iscsi, portal); if (addr == NULL) { iscsi_set_error(iscsi, "Out-of-memory: " "Failed to strdup portal address."); return -1; } host = addr; /* check if we have a target portal group tag */ str = strrchr(host, ','); if (str != NULL) { str[0] = 0; } str = strrchr(host, ':'); if (str != NULL) { if (strchr(str, ']') == NULL) { if (str != NULL) { port = atoi(str+1); str[0] = 0; } } } /* ipv6 in [...] form ? */ if (host[0] == '[') { host ++; str = strchr(host, ']'); if (str == NULL) { iscsi_free(iscsi, addr); iscsi_set_error(iscsi, "Invalid target:%s " "Missing ']' in IPv6 address", portal); return -1; } *str = 0; } /* is it a hostname ? */ if (getaddrinfo(host, NULL, NULL, &ai) != 0) { iscsi_free(iscsi, addr); iscsi_set_error(iscsi, "Invalid target:%s " "Can not resolv into IPv4/v6.", portal); return -1; } iscsi_free(iscsi, addr); memset(&sa, 0, sizeof(sa)); switch (ai->ai_family) { case AF_INET: socksize = sizeof(struct sockaddr_in); memcpy(&sa.sin, ai->ai_addr, socksize); sa.sin.sin_family = AF_INET; sa.sin.sin_port = htons(port); #ifdef HAVE_SOCK_SIN_LEN sa.sin.sin_len = socksize; #endif break; #ifdef HAVE_SOCKADDR_IN6 case AF_INET6: socksize = sizeof(struct sockaddr_in6); memcpy(&sa.sin6, ai->ai_addr, socksize); sa.sin6.sin6_family = AF_INET6; sa.sin6.sin6_port = htons(port); #ifdef HAVE_SOCK_SIN_LEN sa.sin6.sin6_len = socksize; #endif break; #endif default: iscsi_set_error(iscsi, "Unknown address family :%d. " "Only IPv4/IPv6 supported so far.", ai->ai_family); freeaddrinfo(ai); return -1; } iscsi->socket_status_cb = cb; iscsi->connect_data = private_data; if (iscsi->drv->connect(iscsi, &sa, ai->ai_family) < 0) { iscsi_set_error(iscsi, "Couldn't connect transport: %s", iscsi_get_error(iscsi)); freeaddrinfo(ai); return -1; } freeaddrinfo(ai); strncpy(iscsi->connected_portal, portal, MAX_STRING_SIZE); return 0; } static int iscsi_tcp_disconnect(struct iscsi_context *iscsi) { if (iscsi->fd == -1) { iscsi_set_error(iscsi, "Trying to disconnect " "but not connected"); return -1; } close(iscsi->fd); if (!(iscsi->pending_reconnect && iscsi->old_iscsi) && iscsi->connected_portal[0]) { ISCSI_LOG(iscsi, 2, "disconnected from portal %s",iscsi->connected_portal); } iscsi->fd = -1; iscsi->is_connected = 0; iscsi->is_corked = 0; return 0; } int iscsi_disconnect(struct iscsi_context *iscsi) { return iscsi->drv->disconnect(iscsi); } static int iscsi_tcp_get_fd(struct iscsi_context *iscsi) { if (iscsi->old_iscsi) { return iscsi->old_iscsi->fd; } return iscsi->fd; } int iscsi_get_fd(struct iscsi_context *iscsi) { return iscsi->drv->get_fd(iscsi); } static int iscsi_tcp_which_events(struct iscsi_context *iscsi) { int events = iscsi->is_connected ? POLLIN : POLLOUT; if (iscsi->pending_reconnect && iscsi->old_iscsi && time(NULL) < iscsi->next_reconnect) { return 0; } if (iscsi->outqueue_current != NULL || (iscsi->outqueue != NULL && !iscsi->is_corked && (iscsi_serial32_compare(iscsi->outqueue->cmdsn, iscsi->maxcmdsn) <= 0 || iscsi->outqueue->outdata.data[0] & ISCSI_PDU_IMMEDIATE) ) ) { events |= POLLOUT; } return events; } int iscsi_which_events(struct iscsi_context *iscsi) { return iscsi->drv->which_events(iscsi); } int iscsi_queue_length(struct iscsi_context *iscsi) { int i = 0; struct iscsi_pdu *pdu; for (pdu = iscsi->outqueue; pdu; pdu = pdu->next) { i++; } for (pdu = iscsi->waitpdu; pdu; pdu = pdu->next) { i++; } if (iscsi->is_connected == 0) { i++; } return i; } int iscsi_out_queue_length(struct iscsi_context *iscsi) { int i = 0; struct iscsi_pdu *pdu; for (pdu = iscsi->outqueue; pdu; pdu = pdu->next) { i++; } return i; } ssize_t iscsi_iovector_readv_writev(struct iscsi_context *iscsi, struct scsi_iovector *iovector, uint32_t pos, ssize_t count, int do_write) { struct scsi_iovec *iov, *iov2; int niov; uint32_t len2; size_t _len2; ssize_t n; if (iovector->iov == NULL) { errno = EINVAL; return -1; } if (pos < iovector->offset) { iscsi_set_error(iscsi, "iovector reset. pos is smaller than" "current offset"); errno = EINVAL; return -1; } if (iovector->niov <= iovector->consumed) { /* someone issued a read/write but did not provide enough user buffers for all the data. * maybe someone tried to read just 512 bytes off a MMC device? */ errno = EINVAL; return -1; } /* iov is a pointer to the first iovec to pass */ iov = &iovector->iov[iovector->consumed]; pos -= iovector->offset; /* forward until iov points to the first iov to pass */ while (pos >= iov->iov_len) { iovector->offset += iov->iov_len; iovector->consumed++; pos -= iov->iov_len; if (iovector->niov <= iovector->consumed) { errno = EINVAL; return -1; } iov = &iovector->iov[iovector->consumed]; } iov2 = iov; /* iov2 is a pointer to the last iovec to pass */ niov = 1; /* number of iovectors to pass */ len2 = pos + count; /* adjust length of iov2 */ /* forward until iov2 points to the last iovec we pass later. it might happen that we have a lot of iovectors but are limited by count */ while (len2 > iov2->iov_len) { niov++; if (iovector->niov < iovector->consumed + niov) { errno = EINVAL; return -1; } len2 -= iov2->iov_len; iov2 = &iovector->iov[iovector->consumed + niov - 1]; } /* we might limit the length of the last iovec we pass to readv/writev store its orignal length to restore it later */ _len2 = iov2->iov_len; /* adjust base+len of start iovec and len of last iovec */ iov2->iov_len = len2; iov->iov_base = (void*) ((uintptr_t)iov->iov_base + pos); iov->iov_len -= pos; if (do_write) { n = writev(iscsi->fd, (struct iovec*) iov, niov); } else { n = readv(iscsi->fd, (struct iovec*) iov, niov); } /* restore original values */ iov->iov_base = (void*) ((uintptr_t)iov->iov_base - pos); iov->iov_len += pos; iov2->iov_len = _len2; if (n > count) { /* we read/write more bytes than expected, this MUST not happen */ errno = EINVAL; return -1; } return n; } static int iscsi_read_from_socket(struct iscsi_context *iscsi) { struct iscsi_in_pdu *in; ssize_t hdr_size, data_size, count, padding_size; do { hdr_size = ISCSI_HEADER_SIZE(iscsi->header_digest); if (iscsi->incoming == NULL) { iscsi->incoming = iscsi_szmalloc(iscsi, sizeof(struct iscsi_in_pdu)); if (iscsi->incoming == NULL) { iscsi_set_error(iscsi, "Out-of-memory: failed to malloc iscsi_in_pdu"); return -1; } iscsi->incoming->hdr = iscsi_smalloc(iscsi, hdr_size); if (iscsi->incoming->hdr == NULL) { iscsi_set_error(iscsi, "Out-of-memory"); return -1; } } in = iscsi->incoming; /* first we must read the header, including any digests */ if (in->hdr_pos < hdr_size) { /* try to only read the header, the socket is nonblocking, so * no need to limit the read to what is available in the socket */ count = hdr_size - in->hdr_pos; count = recv(iscsi->fd, &in->hdr[in->hdr_pos], count, 0); if (count == 0) { /* remote side has closed the socket. */ return -1; } if (count < 0) { if (errno == EINTR || errno == EAGAIN) { break; } iscsi_set_error(iscsi, "read from socket failed, " "errno:%d", errno); return -1; } in->hdr_pos += count; } if (in->hdr_pos < hdr_size) { /* we don't have the full header yet, so return */ break; } padding_size = iscsi_get_pdu_padding_size(&in->hdr[0]); data_size = iscsi_get_pdu_data_size(&in->hdr[0]) + padding_size; if (data_size < 0 || data_size > (ssize_t)iscsi->initiator_max_recv_data_segment_length) { iscsi_set_error(iscsi, "Invalid data size received from target (%d)", (int)data_size); return -1; } if (data_size != 0) { unsigned char padding_buf[3]; unsigned char *buf = padding_buf; struct scsi_iovector * iovector_in; count = data_size - in->data_pos; /* first try to see if we already have a user buffer */ 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); } else { if (iovector_in == NULL) { if (in->data == NULL) { in->data = iscsi_malloc(iscsi, data_size); if (in->data == NULL) { iscsi_set_error(iscsi, "Out-of-memory: failed to malloc iscsi_in_pdu->data(%d)", (int)data_size); return -1; } } buf = &in->data[in->data_pos]; } count = recv(iscsi->fd, buf, count, 0); } if (count == 0) { /* remote side has closed the socket. */ return -1; } if (count < 0) { if (errno == EINTR || errno == EAGAIN) { break; } iscsi_set_error(iscsi, "read from socket failed, " "errno:%d %s", errno, iscsi_get_error(iscsi)); return -1; } in->data_pos += count; } if (in->data_pos < data_size) { break; } iscsi->incoming = NULL; if (iscsi_process_pdu(iscsi, in) != 0) { iscsi_free_iscsi_in_pdu(iscsi, in); return -1; } iscsi_free_iscsi_in_pdu(iscsi, in); } while (iscsi->tcp_nonblocking && iscsi->waitpdu && iscsi->is_loggedin); 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) { ssize_t count; size_t total; struct iscsi_pdu *pdu; static char padding_buf[3]; int socket_flags = 0; #ifdef MSG_NOSIGNAL socket_flags |= MSG_NOSIGNAL; #elif SO_NOSIGPIPE socket_flags |= SO_NOSIGPIPE; #endif if (iscsi->fd == -1) { iscsi_set_error(iscsi, "trying to write but not connected"); return -1; } while (iscsi->outqueue != NULL || iscsi->outqueue_current != NULL) { if (iscsi->outqueue_current == NULL) { if (iscsi->is_corked) { /* connection is corked we are not allowed to send * additional PDUs */ ISCSI_LOG(iscsi, 6, "iscsi_write_to_socket: socket is corked"); 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 */ ISCSI_LOG(iscsi, 6, "iscsi_write_to_socket: maxcmdsn reached (outqueue[0]->cmdsnd %08x > maxcmdsn %08x)", iscsi->outqueue->cmdsn, iscsi->maxcmdsn); return 0; } /* pop first element of the outqueue */ if (iscsi_serial32_compare(iscsi->outqueue->cmdsn, iscsi->expcmdsn) < 0 && (iscsi->outqueue->outdata.data[0] & 0x3f) != ISCSI_PDU_DATA_OUT) { iscsi_set_error(iscsi, "iscsi_write_to_socket: outqueue[0]->cmdsn < expcmdsn (%08x < %08x) opcode %02x", iscsi->outqueue->cmdsn, iscsi->expcmdsn, iscsi->outqueue->outdata.data[0] & 0x3f); return -1; } iscsi->outqueue_current = iscsi->outqueue; /* 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 since the storage might sent a R2T as soon as it has received the header. if we sent immediate data in a cmd PDU the R2T might get lost otherwise. */ ISCSI_LIST_ADD_END(&iscsi->waitpdu, iscsi->outqueue_current); } } pdu = iscsi->outqueue_current; pdu->outdata.size = (pdu->outdata.size + 3) & 0xfffffffc; /* Write header and any immediate data */ if (pdu->outdata_written < pdu->outdata.size) { count = send(iscsi->fd, pdu->outdata.data + pdu->outdata_written, pdu->outdata.size - pdu->outdata_written, 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->outdata_written += count; } /* if we havent written the full header yet. */ if (pdu->outdata_written != pdu->outdata.size) { return 0; } /* Write any iovectors that might have been passed to us */ while (pdu->payload_written < pdu->payload_len) { struct scsi_iovector* iovector_out; iovector_out = iscsi_get_scsi_task_iovector_out(iscsi, pdu); if (iovector_out == NULL) { iscsi_set_error(iscsi, "Can't find iovector data for DATA-OUT"); return -1; } count = iscsi_iovector_readv_writev(iscsi, iovector_out, pdu->payload_offset + pdu->payload_written, pdu->payload_len - pdu->payload_written, 1); if (count == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { return 0; } iscsi_set_error(iscsi, "Error when writing to " "socket :%d %s", errno, iscsi_get_error(iscsi)); return -1; } pdu->payload_written += count; } total = pdu->payload_len; total = (total + 3) & 0xfffffffc; /* Write padding */ if (pdu->payload_written < total) { count = send(iscsi->fd, padding_buf, total - pdu->payload_written, 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 the full padding yet. */ if (pdu->payload_written != total) { return 0; } if (pdu->flags & ISCSI_PDU_CORK_WHEN_SENT) { iscsi->is_corked = 1; } if (pdu->flags & ISCSI_PDU_DELETE_WHEN_SENT) { iscsi->drv->free_pdu(iscsi, pdu); } iscsi->outqueue_current = NULL; } return 0; } int iscsi_service_reconnect_if_loggedin(struct iscsi_context *iscsi) { if (iscsi->is_loggedin) { if (iscsi_reconnect(iscsi) == 0) { return 0; } } if (iscsi->old_iscsi) { if (!iscsi->pending_reconnect) { iscsi_reconnect_cb(iscsi, SCSI_STATUS_ERROR, NULL, NULL); } return 0; } iscsi_set_error(iscsi, "iscsi_service_reconnect_if_loggedin. Can not " "reconnect right now.\n"); return -1; } static int iscsi_tcp_service(struct iscsi_context *iscsi, int revents) { if (iscsi->fd < 0) { return 0; } if (iscsi->pending_reconnect) { if (time(NULL) >= iscsi->next_reconnect) { return iscsi_reconnect(iscsi); } else { if (iscsi->old_iscsi) { return 0; } } } if (revents & POLLERR) { int err = 0; socklen_t err_size = sizeof(err); if (getsockopt(iscsi->fd, SOL_SOCKET, SO_ERROR, (char *)&err, &err_size) != 0 || err != 0) { if (err == 0) { err = errno; } iscsi_set_error(iscsi, "iscsi_service: socket error " "%s(%d).", strerror(err), err); } else { iscsi_set_error(iscsi, "iscsi_service: POLLERR, " "Unknown socket error."); } if (iscsi->socket_status_cb) { iscsi->socket_status_cb(iscsi, SCSI_STATUS_ERROR, NULL, iscsi->connect_data); iscsi->socket_status_cb = NULL; } return iscsi_service_reconnect_if_loggedin(iscsi); } if (revents & POLLHUP) { iscsi_set_error(iscsi, "iscsi_service: POLLHUP, " "socket error."); if (iscsi->socket_status_cb) { iscsi->socket_status_cb(iscsi, SCSI_STATUS_ERROR, NULL, iscsi->connect_data); iscsi->socket_status_cb = NULL; } return iscsi_service_reconnect_if_loggedin(iscsi); } if (iscsi->is_connected == 0 && revents&POLLOUT) { int err = 0; socklen_t err_size = sizeof(err); struct sockaddr_in local; socklen_t local_l = sizeof(local); if (getsockopt(iscsi->fd, SOL_SOCKET, SO_ERROR, (char *)&err, &err_size) != 0 || err != 0) { if (err == 0) { err = errno; } iscsi_set_error(iscsi, "iscsi_service: socket error " "%s(%d) while connecting.", strerror(err), err); if (iscsi->socket_status_cb) { iscsi->socket_status_cb(iscsi, SCSI_STATUS_ERROR, NULL, iscsi->connect_data); iscsi->socket_status_cb = NULL; } return iscsi_service_reconnect_if_loggedin(iscsi); } if (getsockname(iscsi->fd, (struct sockaddr *) &local, &local_l) == 0) { ISCSI_LOG(iscsi, 2, "connection established (%s:%u -> %s)", inet_ntoa(local.sin_addr), (unsigned)ntohs(local.sin_port),iscsi->connected_portal); } iscsi->is_connected = 1; if (iscsi->socket_status_cb) { iscsi->socket_status_cb(iscsi, SCSI_STATUS_GOOD, NULL, iscsi->connect_data); iscsi->socket_status_cb = NULL; } return 0; } if (revents & POLLIN) { if (iscsi_read_from_socket(iscsi) != 0) { return iscsi_service_reconnect_if_loggedin(iscsi); } } if (revents & POLLOUT) { if (iscsi_write_to_socket(iscsi) != 0) { return iscsi_service_reconnect_if_loggedin(iscsi); } } iscsi_timeout_scan(iscsi); return 0; } int iscsi_service(struct iscsi_context *iscsi, int revents) { return iscsi->drv->service(iscsi, revents); } static int iscsi_tcp_queue_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu) { if (pdu == NULL) { iscsi_set_error(iscsi, "trying to queue NULL pdu"); return -1; } iscsi_add_to_outqueue(iscsi, pdu); return 0; } void iscsi_free_iscsi_in_pdu(struct iscsi_context *iscsi, struct iscsi_in_pdu *in) { iscsi_sfree(iscsi, in->hdr); iscsi_free(iscsi, in->data); in->data=NULL; iscsi_sfree(iscsi, in); in=NULL; } void iscsi_set_tcp_syncnt(struct iscsi_context *iscsi, int value) { iscsi->tcp_syncnt=value; ISCSI_LOG(iscsi, 2, "TCP_SYNCNT will be set to %d on next socket creation",value); } void iscsi_set_tcp_user_timeout(struct iscsi_context *iscsi, int value) { iscsi->tcp_user_timeout=value; ISCSI_LOG(iscsi, 2, "TCP_USER_TIMEOUT will be set to %dms on next socket creation",value); } void iscsi_set_tcp_keepidle(struct iscsi_context *iscsi, int value) { iscsi->tcp_keepidle=value; ISCSI_LOG(iscsi, 2, "TCP_KEEPIDLE will be set to %d on next socket creation",value); } void iscsi_set_tcp_keepcnt(struct iscsi_context *iscsi, int value) { iscsi->tcp_keepcnt=value; ISCSI_LOG(iscsi, 2, "TCP_KEEPCNT will be set to %d on next socket creation",value); } void iscsi_set_tcp_keepintvl(struct iscsi_context *iscsi, int value) { iscsi->tcp_keepintvl=value; ISCSI_LOG(iscsi, 2, "TCP_KEEPINTVL will be set to %d on next socket creation",value); } int iscsi_set_tcp_keepalive(struct iscsi_context *iscsi, int idle _U_, int count _U_, int interval _U_) { #ifdef SO_KEEPALIVE int value = 1; if (setsockopt(iscsi->fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&value, sizeof(value)) != 0) { iscsi_set_error(iscsi, "TCP: Failed to set socket option SO_KEEPALIVE. Error %s(%d)", strerror(errno), errno); return -1; } ISCSI_LOG(iscsi, 3, "SO_KEEPALIVE set to %d",value); #ifdef TCP_KEEPCNT if (set_tcp_sockopt(iscsi->fd, TCP_KEEPCNT, count) != 0) { iscsi_set_error(iscsi, "TCP: Failed to set tcp keepalive count. Error %s(%d)", strerror(errno), errno); return -1; } ISCSI_LOG(iscsi, 3, "TCP_KEEPCNT set to %d",count); #endif #ifdef TCP_KEEPINTVL if (set_tcp_sockopt(iscsi->fd, TCP_KEEPINTVL, interval) != 0) { iscsi_set_error(iscsi, "TCP: Failed to set tcp keepalive interval. Error %s(%d)", strerror(errno), errno); return -1; } ISCSI_LOG(iscsi, 3, "TCP_KEEPINTVL set to %d",interval); #endif #ifdef TCP_KEEPIDLE if (set_tcp_sockopt(iscsi->fd, TCP_KEEPIDLE, idle) != 0) { iscsi_set_error(iscsi, "TCP: Failed to set tcp keepalive idle. Error %s(%d)", strerror(errno), errno); return -1; } ISCSI_LOG(iscsi, 3, "TCP_KEEPIDLE set to %d",idle); #endif #endif return 0; } void iscsi_set_bind_interfaces(struct iscsi_context *iscsi, char * interfaces _U_) { #if __linux strncpy(iscsi->bind_interfaces,interfaces,MAX_STRING_SIZE); iscsi->bind_interfaces_cnt=0; char * pchr = interfaces; char * pchr2 = NULL; do { pchr2 = strchr(pchr,','); if (pchr2) {pchr=pchr2+1;} iscsi->bind_interfaces_cnt++; } while (pchr2); ISCSI_LOG(iscsi,2,"will bind to one of the following %d interface(s) on next socket creation: %s",iscsi->bind_interfaces_cnt,interfaces); if (!iface_rr) iface_rr=rand()%iscsi->bind_interfaces_cnt+1; #else ISCSI_LOG(iscsi,1,"binding to an interface is not supported on your OS"); #endif } #if defined(_MSC_VER) && _MSC_VER < 1900 static iscsi_transport iscsi_transport_tcp = { iscsi_tcp_connect, iscsi_tcp_queue_pdu, iscsi_tcp_new_pdu, iscsi_tcp_disconnect, iscsi_tcp_free_pdu, iscsi_tcp_service, iscsi_tcp_get_fd, iscsi_tcp_which_events, }; #else static iscsi_transport iscsi_transport_tcp = { .connect = iscsi_tcp_connect, .queue_pdu = iscsi_tcp_queue_pdu, .new_pdu = iscsi_tcp_new_pdu, .disconnect = iscsi_tcp_disconnect, .free_pdu = iscsi_tcp_free_pdu, .service = iscsi_tcp_service, .get_fd = iscsi_tcp_get_fd, .which_events = iscsi_tcp_which_events, }; #endif void iscsi_init_tcp_transport(struct iscsi_context *iscsi) { iscsi->drv = &iscsi_transport_tcp; iscsi->transport = TCP_TRANSPORT; }