Commit cb522f25 authored by Rusty Russell's avatar Rusty Russell

iscsi: new module from Ronnie.

parent 0473813a
LIBS=
CC=gcc
CFLAGS=-g -O0 -Wall -W -I../.. "-D_U_=__attribute__((unused))"
LIBISCSI_OBJ = socket.o init.o login.o nop.o pdu.o discovery.o scsi-command.o scsi-lowlevel.o
all: tools/iscsiclient
tools/iscsiclient: tools/iscsiclient.o libiscsi.a
$(CC) $(CFLAGS) -o $@ tools/iscsiclient.o libiscsi.a $(LIBS)
libiscsi.a: $(LIBISCSI_OBJ)
@echo Creating library $@
ar r libiscsi.a $(LIBISCSI_OBJ)
ranlib libiscsi.a
tools/iscsiclient.o: tools/iscsiclient.c
@echo Compiling $@
$(CC) $(CFLAGS) -c tools/iscsiclient.c -o $@
socket.o: socket.c iscsi.h iscsi-private.h
init.o: init.c iscsi.h iscsi-private.h
login.o: login.c iscsi.h iscsi-private.h
pdu.o: pdu.c iscsi.h iscsi-private.h
nop.o: nop.c iscsi.h iscsi-private.h
discovery.o: discovery.c iscsi.h iscsi-private.h
scsi-command.o: scsi-command.c iscsi.h iscsi-private.h
scsi-lowlevel.o: scsi-lowlevel.c scsi-lowlevel.h
clean:
rm -f tools/iscsiclient
rm -f *.o
rm -f libiscsi.a
#include <stdio.h>
#include <string.h>
/**
* iscsi - async library for iscsi functionality
*
* The iscsi module is a work in progress.
*
* It aims to become a full async library for iscsi functionality,
* including all features required to establish and maintain a iscsi
* session, as well as a low level scsi library to create scsi cdb's
* and parse/unmarshall data-in structures.
*
* License: GPL (3 or any later version)
* Author: Ronnie Sahlberg <ronniesahlberg@gmail.com>
*/
int main(int argc, char *argv[])
{
if (argc != 2)
return 1;
if (strcmp(argv[1], "depends") == 0) {
return 0;
}
return 1;
}
/*
Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
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 3 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 <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include "iscsi.h"
#include "iscsi-private.h"
int iscsi_discovery_async(struct iscsi_context *iscsi, iscsi_command_cb cb, void *private_data)
{
struct iscsi_pdu *pdu;
char *str;
if (iscsi == NULL) {
printf("trying to send text on NULL context\n");
return -1;
}
if (iscsi->session_type != ISCSI_SESSION_DISCOVERY) {
printf("Trying to do discovery on non-discovery session\n");
return -2;
}
pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_TEXT_REQUEST, ISCSI_PDU_TEXT_RESPONSE);
if (pdu == NULL) {
printf("Failed to allocate text pdu\n");
return -3;
}
/* immediate */
iscsi_pdu_set_immediate(pdu);
/* flags */
iscsi_pdu_set_pduflags(pdu, ISCSI_PDU_TEXT_FINAL);
/* target transfer tag */
iscsi_pdu_set_ttt(pdu, 0xffffffff);
/* sendtargets */
str = "SendTargets=All";
if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -4;
}
pdu->callback = cb;
pdu->private_data = private_data;
if (iscsi_queue_pdu(iscsi, pdu) != 0) {
printf("failed to queue iscsi text pdu\n");
iscsi_free_pdu(iscsi, pdu);
return -5;
}
return 0;
}
static void iscsi_free_discovery_addresses(struct iscsi_discovery_address *addresses)
{
while (addresses != NULL) {
struct iscsi_discovery_address *next = addresses->next;
if (addresses->target_name != NULL) {
free(discard_const(addresses->target_name));
addresses->target_name = NULL;
}
if (addresses->target_address != NULL) {
free(discard_const(addresses->target_address));
addresses->target_address = NULL;
}
addresses->next = NULL;
free(addresses);
addresses = next;
}
}
int iscsi_process_text_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, const unsigned char *hdr, int size)
{
struct iscsi_discovery_address *targets = NULL;
/* verify the response looks sane */
if (hdr[1] != ISCSI_PDU_TEXT_FINAL) {
printf("unsupported flags in text reply %02x\n", hdr[1]);
pdu->callback(iscsi, ISCSI_STATUS_ERROR, NULL, pdu->private_data);
return -1;
}
/* skip past the header */
hdr += ISCSI_HEADER_SIZE;
size -= ISCSI_HEADER_SIZE;
while (size > 0) {
int len;
len = strlen((char *)hdr);
if (len == 0) {
break;
}
if (len > size) {
printf("len > size when parsing discovery data %d>%d\n", len, size);
pdu->callback(iscsi, ISCSI_STATUS_ERROR, NULL, pdu->private_data);
iscsi_free_discovery_addresses(targets);
return -1;
}
/* parse the strings */
if (!strncmp((char *)hdr, "TargetName=", 11)) {
struct iscsi_discovery_address *target;
target = malloc(sizeof(struct iscsi_discovery_address));
if (target == NULL) {
printf("Failed to allocate data for new discovered target\n");
pdu->callback(iscsi, ISCSI_STATUS_ERROR, NULL, pdu->private_data);
iscsi_free_discovery_addresses(targets);
return -1;
}
bzero(target, sizeof(struct iscsi_discovery_address));
target->target_name = strdup((char *)hdr+11);
if (target->target_name == NULL) {
printf("Failed to allocate data for new discovered target name\n");
pdu->callback(iscsi, ISCSI_STATUS_ERROR, NULL, pdu->private_data);
free(target);
target = NULL;
iscsi_free_discovery_addresses(targets);
return -1;
}
target->next = targets;
targets = target;
} else if (!strncmp((char *)hdr, "TargetAddress=", 14)) {
targets->target_address = strdup((char *)hdr+14);
if (targets->target_address == NULL) {
printf("Failed to allocate data for new discovered target address\n");
pdu->callback(iscsi, ISCSI_STATUS_ERROR, NULL, pdu->private_data);
iscsi_free_discovery_addresses(targets);
return -1;
}
} else {
printf("Dont know how to handle discovery string : %s\n", hdr);
pdu->callback(iscsi, ISCSI_STATUS_ERROR, NULL, pdu->private_data);
iscsi_free_discovery_addresses(targets);
return -1;
}
hdr += len + 1;
size -= len + 1;
}
pdu->callback(iscsi, ISCSI_STATUS_GOOD, targets, pdu->private_data);
iscsi_free_discovery_addresses(targets);
return 0;
}
/*
Unix SMB/CIFS implementation.
some simple double linked list macros
Copyright (C) Andrew Tridgell 1998-2010
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 3 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 <http://www.gnu.org/licenses/>.
*/
/* To use these macros you must have a structure containing a next and
prev pointer */
#ifndef _DLINKLIST_H
#define _DLINKLIST_H
/*
February 2010 - changed list format to have a prev pointer from the
list head. This makes DLIST_ADD_END() O(1) even though we only have
one list pointer.
The scheme is as follows:
1) with no entries in the list:
list_head == NULL
2) with 1 entry in the list:
list_head->next == NULL
list_head->prev == list_head
3) with 2 entries in the list:
list_head->next == element2
list_head->prev == element2
element2->prev == list_head
element2->next == NULL
4) with N entries in the list:
list_head->next == element2
list_head->prev == elementN
elementN->prev == element{N-1}
elementN->next == NULL
This allows us to find the tail of the list by using
list_head->prev, which means we can add to the end of the list in
O(1) time
Note that the 'type' arguments below are no longer needed, but
are kept for now to prevent an incompatible argument change
*/
/*
add an element at the front of a list
*/
#define DLIST_ADD(list, p) \
do { \
if (!(list)) { \
(p)->prev = (list) = (p); \
(p)->next = NULL; \
} else { \
(p)->prev = (list)->prev; \
(list)->prev = (p); \
(p)->next = (list); \
(list) = (p); \
} \
} while (0)
/*
remove an element from a list
Note that the element doesn't have to be in the list. If it
isn't then this is a no-op
*/
#define DLIST_REMOVE(list, p) \
do { \
if ((p) == (list)) { \
if ((p)->next) (p)->next->prev = (p)->prev; \
(list) = (p)->next; \
} else if ((list) && (p) == (list)->prev) { \
(p)->prev->next = NULL; \
(list)->prev = (p)->prev; \
} else { \
if ((p)->prev) (p)->prev->next = (p)->next; \
if ((p)->next) (p)->next->prev = (p)->prev; \
} \
if ((p) != (list)) (p)->next = (p)->prev = NULL; \
} while (0)
/*
find the head of the list given any element in it.
Note that this costs O(N), so you should avoid this macro
if at all possible!
*/
#define DLIST_HEAD(p, result_head) \
do { \
(result_head) = (p); \
while (DLIST_PREV(result_head)) (result_head) = (result_head)->prev; \
} while(0)
/* return the last element in the list */
#define DLIST_TAIL(list) ((list)?(list)->prev:NULL)
/* return the previous element in the list. */
#define DLIST_PREV(p) (((p)->prev && (p)->prev->next != NULL)?(p)->prev:NULL)
/* insert 'p' after the given element 'el' in a list. If el is NULL then
this is the same as a DLIST_ADD() */
#define DLIST_ADD_AFTER(list, p, el) \
do { \
if (!(list) || !(el)) { \
DLIST_ADD(list, p); \
} else { \
(p)->prev = (el); \
(p)->next = (el)->next; \
(el)->next = (p); \
if ((p)->next) (p)->next->prev = (p); \
if ((list)->prev == (el)) (list)->prev = (p); \
}\
} while (0)
/*
add to the end of a list.
Note that 'type' is ignored
*/
#define DLIST_ADD_END(list, p, type) \
do { \
if (!(list)) { \
DLIST_ADD(list, p); \
} else { \
DLIST_ADD_AFTER(list, p, (list)->prev); \
} \
} while (0)
/* promote an element to the from of a list */
#define DLIST_PROMOTE(list, p) \
do { \
DLIST_REMOVE(list, p); \
DLIST_ADD(list, p); \
} while (0)
/*
demote an element to the end of a list.
Note that 'type' is ignored
*/
#define DLIST_DEMOTE(list, p, type) \
do { \
DLIST_REMOVE(list, p); \
DLIST_ADD_END(list, p, NULL); \
} while (0)
/*
concatenate two lists - putting all elements of the 2nd list at the
end of the first list.
Note that 'type' is ignored
*/
#define DLIST_CONCATENATE(list1, list2, type) \
do { \
if (!(list1)) { \
(list1) = (list2); \
} else { \
(list1)->prev->next = (list2); \
if (list2) { \
void *_tmplist = (void *)(list1)->prev; \
(list1)->prev = (list2)->prev; \
(list2)->prev = _tmplist; \
} \
} \
} while (0)
#endif /* _DLINKLIST_H */
/*
Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include "iscsi.h"
#include "iscsi-private.h"
#include "dlinklist.h"
struct iscsi_context *iscsi_create_context(const char *initiator_name)
{
struct iscsi_context *iscsi;
iscsi = malloc(sizeof(struct iscsi_context));
if (iscsi == NULL) {
printf("Failed to allocate iscsi context\n");
return NULL;
}
bzero(iscsi, sizeof(struct iscsi_context));
iscsi->initiator_name = strdup(initiator_name);
if (iscsi->initiator_name == NULL) {
printf("Failed to allocate initiator name context\n");
free(iscsi);
return NULL;
}
iscsi->fd = -1;
/* use a "random" isid */
srandom(getpid() ^ time(NULL));
iscsi_set_random_isid(iscsi);
return iscsi;
}
int iscsi_set_random_isid(struct iscsi_context *iscsi)
{
iscsi->isid[0] = 0x80;
iscsi->isid[1] = random()&0xff;
iscsi->isid[2] = random()&0xff;
iscsi->isid[3] = random()&0xff;
iscsi->isid[4] = 0;
iscsi->isid[5] = 0;
return 0;
}
int iscsi_set_alias(struct iscsi_context *iscsi, const char *alias)
{
if (iscsi == NULL) {
printf("Context is NULL when adding alias\n");
return -1;
}
if (iscsi->is_loggedin != 0) {
printf("Already logged in when adding alias\n");
return -2;
}
if (iscsi->alias != NULL) {
free(discard_const(iscsi->alias));
iscsi->alias = NULL;
}
iscsi->alias = strdup(alias);
if (iscsi->alias == NULL) {
printf("Failed to allocate alias name\n");
return -3;
}
return 0;
}
int iscsi_set_targetname(struct iscsi_context *iscsi, const char *target_name)
{
if (iscsi == NULL) {
printf("Context is NULL when adding targetname\n");
return -1;
}
if (iscsi->is_loggedin != 0) {
printf("Already logged in when adding targetname\n");
return -2;
}
if (iscsi->target_name != NULL) {
free(discard_const(iscsi->target_name));
iscsi->target_name= NULL;
}
iscsi->target_name = strdup(target_name);
if (iscsi->target_name == NULL) {
printf("Failed to allocate target name\n");
return -3;
}
return 0;
}
int iscsi_destroy_context(struct iscsi_context *iscsi)
{
struct iscsi_pdu *pdu;
if (iscsi == NULL) {
return 0;
}
if (iscsi->initiator_name != NULL) {
free(discard_const(iscsi->initiator_name));
iscsi->initiator_name = NULL;
}
if (iscsi->alias != NULL) {
free(discard_const(iscsi->alias));
iscsi->alias = NULL;
}
if (iscsi->is_loggedin != 0) {
printf("deswtroying context while logged in\n");
}
if (iscsi->fd != -1) {
iscsi_disconnect(iscsi);
}
if (iscsi->inbuf != NULL) {
free(iscsi->inbuf);
iscsi->inbuf = NULL;
iscsi->insize = 0;
iscsi->inpos = 0;
}
while ((pdu = iscsi->outqueue)) {
DLIST_REMOVE(iscsi->outqueue, pdu);
pdu->callback(iscsi, ISCSI_STATUS_CANCELLED, NULL, pdu->private_data);
iscsi_free_pdu(iscsi, pdu);
}
while ((pdu = iscsi->waitpdu)) {
DLIST_REMOVE(iscsi->waitpdu, pdu);
pdu->callback(iscsi, ISCSI_STATUS_CANCELLED, NULL, pdu->private_data);
iscsi_free_pdu(iscsi, pdu);
}
free(iscsi);
return 0;
}
/*
Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#ifndef _U_
#define _U_
#endif
#ifndef discard_const
#define discard_const(ptr) ((void *)((intptr_t)(ptr)))
#endif
struct iscsi_context {
const char *initiator_name;
const char *target_name;
const char *alias;
enum iscsi_session_type session_type;
unsigned char isid[6];
uint32_t itt;
uint32_t cmdsn;
uint32_t statsn;
int fd;
int is_connected;
int is_loggedin;
iscsi_command_cb connect_cb;
void *connect_data;
struct iscsi_pdu *outqueue;
struct iscsi_pdu *waitpdu;
int insize;
int inpos;
unsigned char *inbuf;
};
#define ISCSI_HEADER_SIZE 48
#define ISCSI_PDU_IMMEDIATE 0x40
#define ISCSI_PDU_TEXT_FINAL 0x80
#define ISCSI_PDU_TEXT_CONTINUE 0x40
#define ISCSI_PDU_LOGIN_TRANSIT 0x80
#define ISCSI_PDU_LOGIN_CONTINUE 0x40
#define ISCSI_PDU_LOGIN_CSG_SECNEG 0x00
#define ISCSI_PDU_LOGIN_CSG_OPNEG 0x04
#define ISCSI_PDU_LOGIN_CSG_FF 0x0c
#define ISCSI_PDU_LOGIN_NSG_SECNEG 0x00
#define ISCSI_PDU_LOGIN_NSG_OPNEG 0x01
#define ISCSI_PDU_LOGIN_NSG_FF 0x03
#define ISCSI_PDU_SCSI_FINAL 0x80
#define ISCSI_PDU_SCSI_READ 0x40
#define ISCSI_PDU_SCSI_WRITE 0x20
#define ISCSI_PDU_SCSI_ATTR_UNTAGGED 0x00
#define ISCSI_PDU_SCSI_ATTR_SIMPLE 0x01
#define ISCSI_PDU_SCSI_ATTR_ORDERED 0x02
#define ISCSI_PDU_SCSI_ATTR_HEADOFQUEUE 0x03
#define ISCSI_PDU_SCSI_ATTR_ACA 0x04
#define ISCSI_PDU_DATA_FINAL 0x80
#define ISCSI_PDU_DATA_ACK_REQUESTED 0x40
#define ISCSI_PDU_DATA_BIDIR_OVERFLOW 0x10
#define ISCSI_PDU_DATA_BIDIR_UNDERFLOW 0x08
#define ISCSI_PDU_DATA_RESIDUAL_OVERFLOW 0x04
#define ISCSI_PDU_DATA_RESIDUAL_UNDERFLOW 0x02
#define ISCSI_PDU_DATA_CONTAINS_STATUS 0x01
enum iscsi_opcode {ISCSI_PDU_NOP_OUT=0x00,
ISCSI_PDU_SCSI_REQUEST=0x01,
ISCSI_PDU_LOGIN_REQUEST=0x03,
ISCSI_PDU_TEXT_REQUEST=0x04,
ISCSI_PDU_LOGOUT_REQUEST=0x06,
ISCSI_PDU_NOP_IN=0x20,
ISCSI_PDU_SCSI_RESPONSE=0x21,
ISCSI_PDU_LOGIN_RESPONSE=0x23,
ISCSI_PDU_TEXT_RESPONSE=0x24,
ISCSI_PDU_DATA_IN=0x25,
ISCSI_PDU_LOGOUT_RESPONSE=0x26};
struct iscsi_pdu {
struct iscsi_pdu *prev, *next;
uint32_t itt;
uint32_t cmdsn;
enum iscsi_opcode response_opcode;
iscsi_command_cb callback;
void *private_data;
int written;
struct iscsi_data outdata;
struct iscsi_data indata;
struct iscsi_scsi_cbdata *scsi_cbdata;
};
void iscsi_free_scsi_cbdata(struct iscsi_scsi_cbdata *scsi_cbdata);
struct iscsi_pdu *iscsi_allocate_pdu(struct iscsi_context *iscsi, enum iscsi_opcode opcode, enum iscsi_opcode response_opcode);
void iscsi_free_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu);
void iscsi_pdu_set_pduflags(struct iscsi_pdu *pdu, unsigned char flags);
void iscsi_pdu_set_immediate(struct iscsi_pdu *pdu);
void iscsi_pdu_set_ttt(struct iscsi_pdu *pdu, uint32_t ttt);
void iscsi_pdu_set_cmdsn(struct iscsi_pdu *pdu, uint32_t cmdsn);
void iscsi_pdu_set_lun(struct iscsi_pdu *pdu, uint32_t lun);
void iscsi_pdu_set_expstatsn(struct iscsi_pdu *pdu, uint32_t expstatsnsn);
void iscsi_pdu_set_expxferlen(struct iscsi_pdu *pdu, uint32_t expxferlen);
int iscsi_pdu_add_data(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, unsigned char *dptr, int dsize);
int iscsi_queue_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu);
int iscsi_add_data(struct iscsi_data *data, unsigned char *dptr, int dsize, int pdualignment);
int iscsi_set_random_isid(struct iscsi_context *iscsi);
struct scsi_task;
void iscsi_pdu_set_cdb(struct iscsi_pdu *pdu, struct scsi_task *task);
int iscsi_get_pdu_size(const unsigned char *hdr);
int iscsi_process_pdu(struct iscsi_context *iscsi, const unsigned char *hdr, int size);
int iscsi_process_login_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, const unsigned char *hdr, int size);
int iscsi_process_text_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, const unsigned char *hdr, int size);
int iscsi_process_logout_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, const unsigned char *hdr, int size);
int iscsi_process_scsi_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, const unsigned char *hdr, int size);
int iscsi_process_scsi_data_in(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, const unsigned char *hdr, int size, int *is_finished);
int iscsi_process_nop_out_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, const unsigned char *hdr, int size);
/*
Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
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 3 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 <http://www.gnu.org/licenses/>.
*/
struct iscsi_context;
struct sockaddr;
/*
* Returns the file descriptor that libiscsi uses.
*/
int iscsi_get_fd(struct iscsi_context *iscsi);
/*
* Returns which events that we need to poll for for the iscsi file descriptor.
*/
int iscsi_which_events(struct iscsi_context *iscsi);
/*
* Called to process the events when events become available for the iscsi file descriptor.
*/
int iscsi_service(struct iscsi_context *iscsi, int revents);
/*
* Create a context for an ISCSI session.
* Initiator_name is the iqn name we want to identify to the target as.
*
* Returns:
* 0: success
* <0: error
*/
struct iscsi_context *iscsi_create_context(const char *initiator_name);
/*
* Destroy an existing ISCSI context and tear down any existing connection.
* Callbacks for any command in flight will be invoked with ISCSI_STATUS_CANCELLED.
*
* Returns:
* 0: success
* <0: error
*/
int iscsi_destroy_context(struct iscsi_context *iscsi);
/*
* Set an optional alias name to identify with when connecting to the target
*
* Returns:
* 0: success
* <0: error
*/
int iscsi_set_alias(struct iscsi_context *iscsi, const char *alias);
/*
* Set the iqn name of the taqget to login to.
* The target name must be set before a normal-login can be initiated.
* Only discovery-logins are possible without setting the target iqn name.
*
* Returns:
* 0: success
* <0: error
*/
int iscsi_set_targetname(struct iscsi_context *iscsi, const char *targetname);
/* Types of icsi sessions. Discovery sessions are used to query for what targets exist behind
* the portal connected to. Normal sessions are used to log in and do I/O to the SCSI LUNs
*/
enum iscsi_session_type {ISCSI_SESSION_DISCOVERY=1, ISCSI_SESSION_NORMAL=2};
/*
* Set the session type for a scsi context.
* Session type can only be set/changed while the iscsi context is not logged in to
* a target.
*
* Returns:
* 0: success
* <0: error
*/
int iscsi_set_session_type(struct iscsi_context *iscsi, enum iscsi_session_type session_type);
/* ISCSI_STATUS_GOOD must map to SCSI_STATUS_GOOD
* and ISCSI_STATUS_CHECK_CONDITION must map to SCSI_STATUS_CHECK_CONDITION
*/
enum icsi_status { ISCSI_STATUS_GOOD =0,
ISCSI_STATUS_CHECK_CONDITION =2,
ISCSI_STATUS_CANCELLED =0x0f000000,
ISCSI_STATUS_ERROR =0x0f000001 };
/*
* Generic callback for completion of iscsi_*_async().
* command_data depends on status.
*/
typedef void (*iscsi_command_cb)(struct iscsi_context *iscsi, int status, void *command_data, void *private_data);
/*
* Asynchronous call to connect a TCP connection to the target-host/port
*
* Returns:
* 0 if the call was initiated and a connection will be attempted. Result of the connection will be reported
* through the callback function.
* <0 if there was an error. The callback function will not be invoked.
*
* This command is unique in that the callback can be invoked twice.
*
* Callback parameters :
* status can be either of :
* ISCSI_STATUS_GOOD : Connection was successful. Command_data is NULL.
* In this case the callback will be invoked a second time once the connection
* is torn down.
*
* ISCSI_STATUS_ERROR : Either failed to establish the connection, or an already established connection
* has failed with an error.
*
* The callback will NOT be invoked if the session is explicitely torn down through a call to
* iscsi_disconnect() or iscsi_destroy_context().
*/
int iscsi_connect_async(struct iscsi_context *iscsi, const char *target, iscsi_command_cb cb, void *private_data);
/*
* Disconnect a connection to a target.
* You can not disconnect while being logged in to a target.
*
* Returns:
* 0 disconnect was successful
* <0 error
*/
int iscsi_disconnect(struct iscsi_context *iscsi);
/*
* Asynchronous call to perform an ISCSI login.
*
* Returns:
* 0 if the call was initiated and a login will be attempted. Result of the login will be reported
* through the callback function.
* <0 if there was an error. The callback function will not be invoked.
*
* Callback parameters :
* status can be either of :
* ISCSI_STATUS_GOOD : login was successful. Command_data is always NULL.
* ISCSI_STATUS_CANCELLED: login was aborted. Command_data is NULL.
* ISCSI_STATUS_ERROR : login failed. Command_data is NULL.
*/
int iscsi_login_async(struct iscsi_context *iscsi, iscsi_command_cb cb, void *private_data);
/*
* Asynchronous call to perform an ISCSI logout.
*
* Returns:
* 0 if the call was initiated and a logout will be attempted. Result of the logout will be reported
* through the callback function.
* <0 if there was an error. The callback function will not be invoked.
*
* Callback parameters :
* status can be either of :
* ISCSI_STATUS_GOOD : logout was successful. Command_data is always NULL.
* ISCSI_STATUS_CANCELLED: logout was aborted. Command_data is NULL.
*/
int iscsi_logout_async(struct iscsi_context *iscsi, iscsi_command_cb cb, void *private_data);
/*
* Asynchronous call to perform an ISCSI discovery.
*
* discoveries can only be done on connected and logged in discovery sessions.
*
* Returns:
* 0 if the call was initiated and a discovery will be attempted. Result of the logout will be reported
* through the callback function.
* <0 if there was an error. The callback function will not be invoked.
*
* Callback parameters :
* status can be either of :
* ISCSI_STATUS_GOOD : Discovery was successful. Command_data is a pointer to a
* iscsi_discovery_address list of structures.
* This list of structures is only valid for the duration of the
* callback and all data will be freed once the callback returns.
* ISCSI_STATUS_CANCELLED: Discovery was aborted. Command_data is NULL.
*/
int iscsi_discovery_async(struct iscsi_context *iscsi, iscsi_command_cb cb, void *private_data);
struct iscsi_discovery_address {
struct iscsi_discovery_address *next;
const char *target_name;
const char *target_address;
};
/*
* Asynchronous call to perform an ISCSI NOP-OUT call
*
* Returns:
* 0 if the call was initiated and a nop-out will be attempted. Result will be reported
* through the callback function.
* <0 if there was an error. The callback function will not be invoked.
*
* Callback parameters :
* status can be either of :
* ISCSI_STATUS_GOOD : NOP-OUT was successful and the server responded with a NOP-IN
* callback_data is a iscsi_data structure containing the data returned from
* the server.
* ISCSI_STATUS_CANCELLED: Discovery was aborted. Command_data is NULL.
*/
int iscsi_nop_out_async(struct iscsi_context *iscsi, iscsi_command_cb cb, unsigned char *data, int len, void *private_data);
/* These are the possible status values for the callbacks for scsi commands.
* The content of command_data depends on the status type.
*
* status :
* ISCSI_STATUS_GOOD the scsi command completed successfullt on the target.
* If this scsi command returns DATA-IN, that data is stored in an scsi_task structure
* returned in the command_data parameter. This buffer will be automatically freed once the callback
* returns.
*
* ISCSI_STATUS_CHECK_CONDITION the scsi command failed with a scsi sense.
* Command_data contains a struct scsi_task. When the callback returns, this buffer
* will automatically become freed.
*
* ISCSI_STATUS_CANCELLED the scsi command was aborted. Command_data is NULL.
*
* ISCSI_STATUS_ERROR the command failed. Command_data is NULL.
*/
struct iscsi_data {
int size;
unsigned char *data;
};
/*
* Async commands for SCSI
*/
int iscsi_reportluns_async(struct iscsi_context *iscsi, iscsi_command_cb cb, int report_type, int alloc_len, void *private_data);
int iscsi_testunitready_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, void *private_data);
int iscsi_inquiry_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, int evpd, int page_code, int maxsize, void *private_data);
int iscsi_readcapacity10_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, int lba, int pmi, void *private_data);
int iscsi_read10_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, int lba, int datalen, int blocksize, void *private_data);
int iscsi_write10_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, unsigned char *data, int datalen, int lba, int fua, int fuanv, int blocksize, void *private_data);
int iscsi_modesense6_async(struct iscsi_context *iscsi, int lun, iscsi_command_cb cb, int dbd, int pc, int page_code, int sub_page_code, unsigned char alloc_len, void *private_data);
/*
Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
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 3 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 <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include "iscsi.h"
#include "iscsi-private.h"
int iscsi_login_async(struct iscsi_context *iscsi, iscsi_command_cb cb, void *private_data)
{
struct iscsi_pdu *pdu;
char *str;
int ret;
if (iscsi == NULL) {
printf("trying to login on NULL context\n");
return -1;
}
if (iscsi->is_loggedin != 0) {
printf("trying to login while already logged in\n");
return -2;
}
switch (iscsi->session_type) {
case ISCSI_SESSION_DISCOVERY:
case ISCSI_SESSION_NORMAL:
break;
default:
printf("trying to login without setting session type\n");
return -3;
}
pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_LOGIN_REQUEST, ISCSI_PDU_LOGIN_RESPONSE);
if (pdu == NULL) {
printf("Failed to allocate login pdu\n");
return -4;
}
/* login request */
iscsi_pdu_set_immediate(pdu);
/* flags */
iscsi_pdu_set_pduflags(pdu, ISCSI_PDU_LOGIN_TRANSIT|ISCSI_PDU_LOGIN_CSG_OPNEG|ISCSI_PDU_LOGIN_NSG_FF);
/* initiator name */
if (asprintf(&str, "InitiatorName=%s", iscsi->initiator_name) == -1) {
printf("asprintf failed\n");
iscsi_free_pdu(iscsi, pdu);
return -5;
}
ret = iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1);
free(str);
if (ret != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -6;
}
/* optional alias */
if (iscsi->alias) {
if (asprintf(&str, "InitiatorAlias=%s", iscsi->alias) == -1) {
printf("asprintf failed\n");
iscsi_free_pdu(iscsi, pdu);
return -7;
}
ret = iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1);
free(str);
if (ret != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -8;
}
}
/* target name */
if (iscsi->session_type == ISCSI_SESSION_NORMAL) {
if (iscsi->target_name == NULL) {
printf("trying normal connect but target name not set\n");
iscsi_free_pdu(iscsi, pdu);
return -9;
}
if (asprintf(&str, "TargetName=%s", iscsi->target_name) == -1) {
printf("asprintf failed\n");
iscsi_free_pdu(iscsi, pdu);
return -10;
}
ret = iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1);
free(str);
if (ret != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -11;
}
}
/* session type */
switch (iscsi->session_type) {
case ISCSI_SESSION_DISCOVERY:
str = "SessionType=Discovery";
break;
case ISCSI_SESSION_NORMAL:
str = "SessionType=Normal";
break;
default:
printf("can not handle sessions %d yet\n", iscsi->session_type);
return -12;
}
if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -13;
}
str = "HeaderDigest=None";
if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -14;
}
str = "DataDigest=None";
if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -15;
}
str = "InitialR2T=Yes";
if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -16;
}
str = "ImmediateData=Yes";
if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -17;
}
str = "MaxBurstLength=262144";
if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -18;
}
str = "FirstBurstLength=262144";
if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -19;
}
str = "MaxRecvDataSegmentLength=262144";
if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -20;
}
str = "DataPDUInOrder=Yes";
if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -21;
}
str = "DataSequenceInOrder=Yes";
if (iscsi_pdu_add_data(iscsi, pdu, (unsigned char *)str, strlen(str)+1) != 0) {
printf("pdu add data failed\n");
iscsi_free_pdu(iscsi, pdu);
return -22;
}
pdu->callback = cb;
pdu->private_data = private_data;
if (iscsi_queue_pdu(iscsi, pdu) != 0) {
printf("failed to queue iscsi login pdu\n");
iscsi_free_pdu(iscsi, pdu);
return -23;
}
return 0;
}
int iscsi_process_login_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, const unsigned char *hdr, int size)
{
int status;
if (size < ISCSI_HEADER_SIZE) {
printf("dont have enough data to read status from login reply\n");
return -1;
}
/* XXX here we should parse the data returned in case the target renegotiated some
* some parameters.
* we should also do proper handshaking if the target is not yet prepared to transition
* to the next stage
*/
status = ntohs(*(uint16_t *)&hdr[36]);
if (status != 0) {
pdu->callback(iscsi, ISCSI_STATUS_ERROR, NULL, pdu->private_data);
return 0;
}
iscsi->statsn = ntohs(*(uint16_t *)&hdr[24]);
iscsi->is_loggedin = 1;
pdu->callback(iscsi, ISCSI_STATUS_GOOD, NULL, pdu->private_data);
return 0;
}
int iscsi_logout_async(struct iscsi_context *iscsi, iscsi_command_cb cb, void *private_data)
{
struct iscsi_pdu *pdu;
if (iscsi == NULL) {
printf("trying to logout on NULL context\n");
return -1;
}
if (iscsi->is_loggedin == 0) {
printf("trying to logout while not logged in\n");
return -2;
}
pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_LOGOUT_REQUEST, ISCSI_PDU_LOGOUT_RESPONSE);
if (pdu == NULL) {
printf("Failed to allocate logout pdu\n");
return -3;
}
/* logout request has the immediate flag set */
iscsi_pdu_set_immediate(pdu);
/* flags : close the session */
iscsi_pdu_set_pduflags(pdu, 0x80);
pdu->callback = cb;
pdu->private_data = private_data;
if (iscsi_queue_pdu(iscsi, pdu) != 0) {
printf("failed to queue iscsi logout pdu\n");
iscsi_free_pdu(iscsi, pdu);
return -4;
}
return 0;
}
int iscsi_process_logout_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, const unsigned char *hdr, int size _U_)
{
iscsi->is_loggedin = 0;
pdu->callback(iscsi, ISCSI_STATUS_GOOD, NULL, pdu->private_data);
return 0;
}
int iscsi_set_session_type(struct iscsi_context *iscsi, enum iscsi_session_type session_type)
{
if (iscsi == NULL) {
printf("Trying to set sesssion type on NULL context\n");
return -1;
}
if (iscsi->is_loggedin) {
printf("trying to set session type while logged in\n");
return -2;
}
iscsi->session_type = session_type;
return 0;
}
/*
Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include "iscsi.h"
#include "iscsi-private.h"
int iscsi_nop_out_async(struct iscsi_context *iscsi, iscsi_command_cb cb, unsigned char *data, int len, void *private_data)
{
struct iscsi_pdu *pdu;
if (iscsi == NULL) {
printf("trying to send nop-out on NULL context\n");
return -1;
}
if (iscsi->is_loggedin == 0) {
printf("trying send nop-out while not logged in\n");
return -2;
}
pdu = iscsi_allocate_pdu(iscsi, ISCSI_PDU_NOP_OUT, ISCSI_PDU_NOP_IN);
if (pdu == NULL) {
printf("Failed to allocate nop-out pdu\n");
return -3;
}
/* immediate flag */
iscsi_pdu_set_immediate(pdu);
/* flags */
iscsi_pdu_set_pduflags(pdu, 0x80);
/* ttt */
iscsi_pdu_set_ttt(pdu, 0xffffffff);
/* lun */
iscsi_pdu_set_lun(pdu, 2);
/* cmdsn is not increased if Immediate delivery*/
iscsi_pdu_set_cmdsn(pdu, iscsi->cmdsn);
pdu->cmdsn = iscsi->cmdsn;
// iscsi->cmdsn++;
pdu->callback = cb;
pdu->private_data = private_data;
if (iscsi_pdu_add_data(iscsi, pdu, data, len) != 0) {
printf("Failed to add outdata to nop-out\n");
iscsi_free_pdu(iscsi, pdu);
return -4;
}
if (iscsi_queue_pdu(iscsi, pdu) != 0) {
printf("failed to queue iscsi nop-out pdu\n");
iscsi_free_pdu(iscsi, pdu);
return -5;
}
return 0;
}
int iscsi_process_nop_out_reply(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, const unsigned char *hdr, int size)
{
struct iscsi_data data;
data.data = NULL;
data.size = 0;
if (size > ISCSI_HEADER_SIZE) {
data.data = discard_const(&hdr[ISCSI_HEADER_SIZE]);
data.size = size - ISCSI_HEADER_SIZE;
}
pdu->callback(iscsi, ISCSI_STATUS_GOOD, &data, pdu->private_data);
return 0;
}
/*
Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <arpa/inet.h>
#include "iscsi.h"
#include "iscsi-private.h"
#include "scsi-lowlevel.h"
#include "dlinklist.h"
struct iscsi_pdu *iscsi_allocate_pdu(struct iscsi_context *iscsi, enum iscsi_opcode opcode, enum iscsi_opcode response_opcode)
{
struct iscsi_pdu *pdu;
pdu = malloc(sizeof(struct iscsi_pdu));
if (pdu == NULL) {
printf("failed to allocate pdu\n");
return NULL;
}
bzero(pdu, sizeof(struct iscsi_pdu));
pdu->outdata.size = ISCSI_HEADER_SIZE;
pdu->outdata.data = malloc(pdu->outdata.size);
if (pdu->outdata.data == NULL) {
printf("failed to allocate pdu header\n");
free(pdu);
pdu = NULL;
return NULL;
}
bzero(pdu->outdata.data, pdu->outdata.size);
/* opcode */
pdu->outdata.data[0] = opcode;
pdu->response_opcode = response_opcode;
/* isid */
if (opcode ==ISCSI_PDU_LOGIN_REQUEST) {
memcpy(&pdu->outdata.data[8], &iscsi->isid[0], 6);
}
/* itt */
*(uint32_t *)&pdu->outdata.data[16] = htonl(iscsi->itt);
pdu->itt = iscsi->itt;
iscsi->itt++;
return pdu;
}
void iscsi_free_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu)
{
if (pdu == NULL) {
printf("trying to free NULL pdu\n");
return;
}
if (pdu->outdata.data) {
free(pdu->outdata.data);
pdu->outdata.data = NULL;
}
if (pdu->indata.data) {
free(pdu->indata.data);
pdu->indata.data = NULL;
}
if (pdu->scsi_cbdata) {
iscsi_free_scsi_cbdata(pdu->scsi_cbdata);
pdu->scsi_cbdata = NULL;
}
free(pdu);
}
int iscsi_add_data(struct iscsi_data *data, unsigned char *dptr, int dsize, int pdualignment)
{
int len, aligned;
unsigned char *buf;
if (dsize == 0) {
printf("Trying to append zero size data to iscsi_data\n");
return -1;
}
len = data->size + dsize;
aligned = len;
if (pdualignment) {
aligned = (aligned+3)&0xfffffffc;
}
buf = malloc(aligned);
if (buf == NULL) {
printf("failed to allocate buffer for %d bytes\n", len);
return -2;
}
memcpy(buf, data->data, data->size);
memcpy(buf + data->size, dptr, dsize);
if (len != aligned) {
/* zero out any padding at the end */
bzero(buf+len, aligned-len);
}
free(data->data);
data->data = buf;
data->size = len;
return 0;
}
int iscsi_pdu_add_data(struct iscsi_context *iscsi, struct iscsi_pdu *pdu, unsigned char *dptr, int dsize)
{
int len, aligned;
unsigned char *buf;
if (pdu == NULL) {
printf("trying to add data to NULL pdu\n");
return -1;
}
if (dsize == 0) {
printf("Trying to append zero size data to pdu\n");
return -2;
}
if (iscsi_add_data(&pdu->outdata, dptr, dsize, 1) != 0) {
printf("failed to add data to pdu buffer\n");
return -3;
}
/* update data segment length */
*(uint32_t *)&pdu->outdata.data[4] = htonl(pdu->outdata.size-ISCSI_HEADER_SIZE);
return 0;
}
int iscsi_get_pdu_size(const unsigned char *hdr)
{
int size;
size = (ntohl(*(uint32_t *)&hdr[4])&0x00ffffff) + ISCSI_HEADER_SIZE;
size = (size+3)&0xfffffffc;
return size;
}
int iscsi_process_pdu(struct iscsi_context *iscsi, const unsigned char *hdr, int size)
{
uint32_t itt;
enum iscsi_opcode opcode;
struct iscsi_pdu *pdu;
uint8_t ahslen;
opcode = hdr[0] & 0x3f;
ahslen = hdr[4];
itt = ntohl(*(uint32_t *)&hdr[16]);
if (ahslen != 0) {
printf("cant handle expanded headers yet\n");
return -1;
}
for (pdu = iscsi->waitpdu; pdu; pdu = pdu->next) {
enum iscsi_opcode expected_response = pdu->response_opcode;
int is_finished = 1;
if (pdu->itt != itt) {
continue;
}
/* we have a special case with scsi-command opcodes, the are replied to by either a scsi-response
* or a data-in, or a combination of both.
*/
if (opcode == ISCSI_PDU_DATA_IN && expected_response == ISCSI_PDU_SCSI_RESPONSE) {
expected_response = ISCSI_PDU_DATA_IN;
}
if (opcode != expected_response) {
printf("Got wrong opcode back for itt:%d got:%d expected %d\n", itt, opcode, pdu->response_opcode);
return -1;
}
switch (opcode) {
case ISCSI_PDU_LOGIN_RESPONSE:
if (iscsi_process_login_reply(iscsi, pdu, hdr, size) != 0) {
DLIST_REMOVE(iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
printf("iscsi login reply failed\n");
return -2;
}
break;
case ISCSI_PDU_TEXT_RESPONSE:
if (iscsi_process_text_reply(iscsi, pdu, hdr, size) != 0) {
DLIST_REMOVE(iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
printf("iscsi text reply failed\n");
return -2;
}
break;
case ISCSI_PDU_LOGOUT_RESPONSE:
if (iscsi_process_logout_reply(iscsi, pdu, hdr, size) != 0) {
DLIST_REMOVE(iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
printf("iscsi logout reply failed\n");
return -3;
}
break;
case ISCSI_PDU_SCSI_RESPONSE:
if (iscsi_process_scsi_reply(iscsi, pdu, hdr, size) != 0) {
DLIST_REMOVE(iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
printf("iscsi response reply failed\n");
return -4;
}
break;
case ISCSI_PDU_DATA_IN:
if (iscsi_process_scsi_data_in(iscsi, pdu, hdr, size, &is_finished) != 0) {
DLIST_REMOVE(iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
printf("iscsi data in failed\n");
return -4;
}
break;
case ISCSI_PDU_NOP_IN:
if (iscsi_process_nop_out_reply(iscsi, pdu, hdr, size) != 0) {
DLIST_REMOVE(iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
printf("iscsi nop-in failed\n");
return -5;
}
break;
default:
printf("Dont know how to handle opcode %d\n", opcode);
return -2;
}
if (is_finished) {
DLIST_REMOVE(iscsi->waitpdu, pdu);
iscsi_free_pdu(iscsi, pdu);
} else {
printf("pdu is not yet finished, let it remain\n");
}
return 0;
}
return 0;
}
void iscsi_pdu_set_pduflags(struct iscsi_pdu *pdu, unsigned char flags)
{
pdu->outdata.data[1] = flags;
}
void iscsi_pdu_set_immediate(struct iscsi_pdu *pdu)
{
pdu->outdata.data[0] |= ISCSI_PDU_IMMEDIATE;
}
void iscsi_pdu_set_ttt(struct iscsi_pdu *pdu, uint32_t ttt)
{
*(uint32_t *)&pdu->outdata.data[20] = htonl(ttt);
}
void iscsi_pdu_set_cmdsn(struct iscsi_pdu *pdu, uint32_t cmdsn)
{
*(uint32_t *)&pdu->outdata.data[24] = htonl(cmdsn);
}
void iscsi_pdu_set_expstatsn(struct iscsi_pdu *pdu, uint32_t expstatsnsn)
{
*(uint32_t *)&pdu->outdata.data[28] = htonl(expstatsnsn);
}
void iscsi_pdu_set_cdb(struct iscsi_pdu *pdu, struct scsi_task *task)
{
bzero(&pdu->outdata.data[32], 16);
memcpy(&pdu->outdata.data[32], task->cdb, task->cdb_size);
}
void iscsi_pdu_set_lun(struct iscsi_pdu *pdu, uint32_t lun)
{
pdu->outdata.data[9] = lun;
}
void iscsi_pdu_set_expxferlen(struct iscsi_pdu *pdu, uint32_t expxferlen)
{
*(uint32_t *)&pdu->outdata.data[20] = htonl(expxferlen);
}
This diff is collapsed.
This diff is collapsed.
/*
Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
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 3 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _U_
#define _U_
#endif
#define SCSI_CDB_MAX_SIZE 16
enum scsi_opcode {SCSI_OPCODE_TESTUNITREADY=0x00,
SCSI_OPCODE_INQUIRY=0x12,
SCSI_OPCODE_MODESENSE6=0x1a,
SCSI_OPCODE_READCAPACITY10=0x25,
SCSI_OPCODE_READ10=0x28,
SCSI_OPCODE_WRITE10=0x2A,
SCSI_OPCODE_REPORTLUNS=0xA0};
/* sense keys */
#define SCSI_SENSE_KEY_ILLEGAL_REQUEST 0x05
#define SCSI_SENSE_KEY_UNIT_ATTENTION 0x06
/* ascq */
#define SCSI_SENSE_ASCQ_INVALID_FIELD_IN_CDB 0x2400
#define SCSI_SENSE_ASCQ_BUS_RESET 0x2900
enum scsi_xfer_dir {SCSI_XFER_NONE=0,
SCSI_XFER_READ=1,
SCSI_XFER_WRITE=2};
struct scsi_reportluns_params {
int report_type;
};
struct scsi_readcapacity10_params {
int lba;
int pmi;
};
struct scsi_inquiry_params {
int evpd;
int page_code;
};
struct scsi_modesense6_params {
int dbd;
int pc;
int page_code;
int sub_page_code;
};
struct scsi_sense {
unsigned char error_type;
unsigned char key;
int ascq;
};
struct scsi_data {
int size;
unsigned char *data;
};
struct scsi_allocated_memory {
struct scsi_allocated_memory *prev, *next;
void *ptr;
};
struct scsi_task {
int cdb_size;
int xfer_dir;
int expxferlen;
unsigned char cdb[SCSI_CDB_MAX_SIZE];
union {
struct scsi_readcapacity10_params readcapacity10;
struct scsi_reportluns_params reportluns;
struct scsi_inquiry_params inquiry;
struct scsi_modesense6_params modesense6;
} params;
struct scsi_sense sense;
struct scsi_data datain;
struct scsi_allocated_memory *mem;
};
void scsi_free_scsi_task(struct scsi_task *task);
/*
* TESTUNITREADY
*/
struct scsi_task *scsi_cdb_testunitready(void);
/*
* REPORTLUNS
*/
#define SCSI_REPORTLUNS_REPORT_ALL_LUNS 0x00
#define SCSI_REPORTLUNS_REPORT_WELL_KNOWN_ONLY 0x01
#define SCSI_REPORTLUNS_REPORT_AVAILABLE_LUNS_ONLY 0x02
struct scsi_reportluns_list {
uint32_t num;
uint16_t luns[0];
};
struct scsi_task *scsi_reportluns_cdb(int report_type, int alloc_len);
/*
* READCAPACITY10
*/
struct scsi_readcapacity10 {
uint32_t lba;
uint32_t block_size;
};
struct scsi_task *scsi_cdb_readcapacity10(int lba, int pmi);
/*
* INQUIRY
*/
enum scsi_inquiry_peripheral_qualifier {SCSI_INQUIRY_PERIPHERAL_QUALIFIER_CONNECTED=0x00,
SCSI_INQUIRY_PERIPHERAL_QUALIFIER_DISCONNECTED=0x01,
SCSI_INQUIRY_PERIPHERAL_QUALIFIER_NOT_SUPPORTED=0x03};
enum scsi_inquiry_peripheral_device_type {SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_DIRECT_ACCESS=0x00,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_SEQUENTIAL_ACCESS=0x01,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_PRINTER=0x02,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_PROCESSOR=0x03,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_WRITE_ONCE=0x04,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_MMC=0x05,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_SCANNER=0x06,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_OPTICAL_MEMORY=0x07,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_MEDIA_CHANGER=0x08,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_COMMUNICATIONS=0x09,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_STORAGE_ARRAY_CONTROLLER=0x0c,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_ENCLOSURE_SERVICES=0x0d,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_SIMPLIFIED_DIRECT_ACCESS=0x0e,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_OPTICAL_CARD_READER=0x0f,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_BRIDGE_CONTROLLER=0x10,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_OSD=0x11,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_AUTOMATION=0x12,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_SEQURITY_MANAGER=0x13,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_WELL_KNOWN_LUN=0x1e,
SCSI_INQUIRY_PERIPHERAL_DEVICE_TYPE_UNKNOWN=0x1f};
struct scsi_inquiry_standard {
enum scsi_inquiry_peripheral_qualifier periperal_qualifier;
enum scsi_inquiry_peripheral_device_type periperal_device_type;
int rmb;
int version;
int normaca;
int hisup;
int response_data_format;
char vendor_identification[8+1];
char product_identification[16+1];
char product_revision_level[4+1];
};
struct scsi_task *scsi_cdb_inquiry(int evpd, int page_code, int alloc_len);
/*
* MODESENSE6
*/
enum scsi_modesense_page_control {SCSI_MODESENSE_PC_CURRENT=0x00,
SCSI_MODESENSE_PC_CHANGEABLE=0x01,
SCSI_MODESENSE_PC_DEFAULT=0x02,
SCSI_MODESENSE_PC_SAVED=0x03};
enum scsi_modesense_page_code {SCSI_MODESENSE_PAGECODE_RETURN_ALL_PAGES=0x3f};
struct scsi_task *scsi_cdb_modesense6(int dbd, enum scsi_modesense_page_control pc, enum scsi_modesense_page_code page_code, int sub_page_code, unsigned char alloc_len);
int scsi_datain_getfullsize(struct scsi_task *task);
void *scsi_datain_unmarshall(struct scsi_task *task);
struct scsi_task *scsi_cdb_read10(int lba, int xferlen, int blocksize);
struct scsi_task *scsi_cdb_write10(int lba, int xferlen, int fua, int fuanv, int blocksize);
/*
Copyright (C) 2010 by Ronnie Sahlberg <ronniesahlberg@gmail.com>
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 3 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include "iscsi.h"
#include "iscsi-private.h"
#include "dlinklist.h"
static void set_nonblocking(int fd)
{
unsigned v;
v = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, v | O_NONBLOCK);
}
int iscsi_connect_async(struct iscsi_context *iscsi, const char *target, iscsi_command_cb cb, void *private_data)
{
int tpgt = -1;
int port = 3260;
char *str;
char *addr;
struct sockaddr_storage s;
struct sockaddr_in *sin = (struct sockaddr_in *)&s;
int socksize;
if (iscsi == NULL) {
printf("Trying to connect NULL context\n");
return -1;
}
if (iscsi->fd != -1) {
printf("Trying to connect but already connected\n");
return -2;
}
addr = strdup(target);
if (addr == NULL) {
printf("failed to strdup target address\n");
return -3;
}
/* check if we have a target portal group tag */
if ((str = rindex(addr, ',')) != NULL) {
tpgt = atoi(str+1);
str[0] = 0;
}
/* XXX need handling for {ipv6 addresses} */
/* for now, assume all is ipv4 */
if ((str = rindex(addr, ':')) != NULL) {
port = atoi(str+1);
str[0] = 0;
}
sin->sin_family = AF_INET;
sin->sin_port = htons(port);
if (inet_pton(AF_INET, addr, &sin->sin_addr) != 1) {
printf("failed to convert to ip address\n");
free(addr);
return -4;
}
free(addr);
switch (s.ss_family) {
case AF_INET:
iscsi->fd = socket(AF_INET, SOCK_STREAM, 0);
socksize = sizeof(struct sockaddr_in);
break;
default:
printf("Unknown family :%d\n", s.ss_family);
return -5;
}
if (iscsi->fd == -1) {
printf("Failed to open socket\n");
return -6;
}
iscsi->connect_cb = cb;
iscsi->connect_data = private_data;
set_nonblocking(iscsi->fd);
if (connect(iscsi->fd, (struct sockaddr *)&s, socksize) != 0 && errno != EINPROGRESS) {
printf("Connect failed errno : %s (%d)\n", strerror(errno), errno);
return -7;
}
return 0;
}
int iscsi_disconnect(struct iscsi_context *iscsi)
{
if (iscsi == NULL) {
printf("Trying to disconnect NULL context\n");
return -1;
}
if (iscsi->is_loggedin != 0) {
printf("Trying to disconnect while logged in\n");
return -2;
}
if (iscsi->fd == -1) {
printf("Trying to disconnect but not connected\n");
return -3;
}
close(iscsi->fd);
iscsi->fd = -1;
iscsi->is_connected = 0;
return 0;
}
int iscsi_get_fd(struct iscsi_context *iscsi)
{
if (iscsi == NULL) {
printf("Trying to get fd for NULL context\n");
return -1;
}
return iscsi->fd;
}
int iscsi_which_events(struct iscsi_context *iscsi)
{
int events = POLLIN;
if (iscsi->is_connected == 0) {
events |= POLLOUT;
}
if (iscsi->outqueue) {
events |= POLLOUT;
}
return events;
}
static int iscsi_read_from_socket(struct iscsi_context *iscsi)
{
int available;
int size;
unsigned char *buf;
ssize_t count;
if (ioctl(iscsi->fd, FIONREAD, &available) != 0) {
printf("ioctl FIONREAD returned error : %d\n", errno);
return -1;
}
if (available == 0) {
printf("no data readable in socket, socket is closed\n");
return -2;
}
size = iscsi->insize - iscsi->inpos + available;
buf = malloc(size);
if (buf == NULL) {
printf("failed to allocate %d bytes for input buffer\n", size);
return -3;
}
if (iscsi->insize > iscsi->inpos) {
memcpy(buf, iscsi->inbuf + iscsi->inpos, iscsi->insize - iscsi->inpos);
iscsi->insize -= iscsi->inpos;
iscsi->inpos = 0;
}
count = read(iscsi->fd, buf + iscsi->insize, available);
if (count == -1) {
if (errno == EINTR) {
free(buf);
buf = NULL;
return 0;
}
printf("read from socket failed, errno:%d\n", errno);
free(buf);
buf = NULL;
return -4;
}
if (iscsi->inbuf != NULL) {
free(iscsi->inbuf);
}
iscsi->inbuf = buf;
iscsi->insize += count;
while (1) {
if (iscsi->insize - iscsi->inpos < 48) {
return 0;
}
count = iscsi_get_pdu_size(iscsi->inbuf + iscsi->inpos);
if (iscsi->insize + iscsi->inpos < count) {
return 0;
}
if (iscsi_process_pdu(iscsi, iscsi->inbuf + iscsi->inpos, count) != 0) {
printf("failed to process pdu\n");
return -5;
}
iscsi->inpos += count;
if (iscsi->inpos == iscsi->insize) {
free(iscsi->inbuf);
iscsi->inbuf = NULL;
iscsi->insize = 0;
iscsi->inpos = 0;
}
if (iscsi->inpos > iscsi->insize) {
printf("inpos > insize. bug!\n");
return -6;
}
}
return 0;
}
static int iscsi_write_to_socket(struct iscsi_context *iscsi)
{
ssize_t count;
if (iscsi == NULL) {
printf("trying to write to socket for NULL context\n");
return -1;
}
if (iscsi->fd == -1) {
printf("trying to write but not connected\n");
return -2;
}
while (iscsi->outqueue != NULL) {
ssize_t total;
total = iscsi->outqueue->outdata.size;
total = (total +3) & 0xfffffffc;
count = write(iscsi->fd, iscsi->outqueue->outdata.data + iscsi->outqueue->written, total - iscsi->outqueue->written);
if (count == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("socket would block, return from write to socket\n");
return 0;
}
printf("Error when writing to socket :%d\n", errno);
return -3;
}
iscsi->outqueue->written += count;
if (iscsi->outqueue->written == total) {
struct iscsi_pdu *pdu = iscsi->outqueue;
DLIST_REMOVE(iscsi->outqueue, pdu);
DLIST_ADD_END(iscsi->waitpdu, pdu, NULL);
}
}
return 0;
}
int iscsi_service(struct iscsi_context *iscsi, int revents)
{
if (revents & POLLERR) {
printf("iscsi_service: POLLERR, socket error\n");
iscsi->connect_cb(iscsi, ISCSI_STATUS_ERROR, NULL, iscsi->connect_data);
return -1;
}
if (revents & POLLHUP) {
printf("iscsi_service: POLLHUP, socket error\n");
iscsi->connect_cb(iscsi, ISCSI_STATUS_ERROR, NULL, iscsi->connect_data);
return -2;
}
if (iscsi->is_connected == 0 && iscsi->fd != -1 && revents&POLLOUT) {
iscsi->is_connected = 1;
iscsi->connect_cb(iscsi, ISCSI_STATUS_GOOD, NULL, iscsi->connect_data);
return 0;
}
if (revents & POLLOUT && iscsi->outqueue != NULL) {
if (iscsi_write_to_socket(iscsi) != 0) {
printf("write to socket failed\n");
return -3;
}
}
if (revents & POLLIN) {
if (iscsi_read_from_socket(iscsi) != 0) {
printf("read from socket failed\n");
return -4;
}
}
return 0;
}
int iscsi_queue_pdu(struct iscsi_context *iscsi, struct iscsi_pdu *pdu)
{
if (iscsi == NULL) {
printf("trying to queue to NULL context\n");
return -1;
}
if (pdu == NULL) {
printf("trying to queue NULL pdu\n");
return -2;
}
DLIST_ADD_END(iscsi->outqueue, pdu, NULL);
return 0;
}
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment