diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-attr.c linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-attr.c --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-attr.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-attr.c 2005-06-15 17:18:33.387472100 -0500 @@ -0,0 +1,313 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2002 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-attr.c,v 1.1.2.17 2005/04/26 17:44:50 mikenc Exp $ + * + * The sysfs host attributes are defined here. + */ +#include +#include + +#include "iscsi-session.h" +#include "iscsi-task.h" +#include "iscsi-sfnet.h" + +static ssize_t +store_do_shutdown(struct class_device *class_dev, const char *buf, size_t count) +{ + iscsi_destroy_host(class_to_shost(class_dev)); + return count; +} + +static ssize_t +store_drop_session(struct class_device *class_dev, const char *buf, size_t count) +{ + struct Scsi_Host *shost = class_to_shost(class_dev); + struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; + + iscsi_drop_session(session); + return count; +} + +static CLASS_DEVICE_ATTR(shutdown, S_IWUSR, NULL, store_do_shutdown); +static CLASS_DEVICE_ATTR(drop_session, S_IWUSR, NULL, store_drop_session); + +static ssize_t +show_session_established(struct class_device *class_dev, char *buf) +{ + struct Scsi_Host *shost = class_to_shost(class_dev); + struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; + + if (test_bit(SESSION_ESTABLISHED, &session->control_bits)) + sprintf(buf, "1"); + else + sprintf(buf, "0"); + return 1; +} +static CLASS_DEVICE_ATTR(session_established, S_IRUGO, + show_session_established, NULL); + +/* + * Macro to show session values specific to this driver + * on the scsi host's class dev. Some of them could also + * be moved to the transport class one day. + */ +#define session_show_function(field, format_string) \ +static ssize_t \ +show_##field (struct class_device *class_dev, char *buf) \ +{ \ + struct Scsi_Host *shost = class_to_shost(class_dev); \ + struct iscsi_session *session; \ + session = (struct iscsi_session *)shost->hostdata; \ + return snprintf(buf, 20, format_string, session->field); \ +} + +#define session_rd_attr(field, format_string) \ + session_show_function(field, format_string) \ +static CLASS_DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); + +session_rd_attr(window_closed, "%lu"); + +#define session_store_tmo_function(field, format_string) \ +static ssize_t \ +store_##field(struct class_device *class_dev, const char *buf, \ + size_t count) \ +{ \ + struct Scsi_Host *shost = class_to_shost(class_dev); \ + struct iscsi_session *session; \ + int timeout; \ + \ + session = (struct iscsi_session *)shost->hostdata; \ + sscanf(buf, "%d\n", &timeout); \ + iscsi_update_##field(session, timeout); \ + return count; \ +} + +#define session_tmo_attr(field, format_string) \ + session_show_function(field, format_string) \ + session_store_tmo_function(field, format_string) \ +static CLASS_DEVICE_ATTR(field, S_IRUGO | S_IWUSR, \ + show_##field, store_##field); + +session_tmo_attr(login_timeout, "%d"); +session_tmo_attr(active_timeout, "%d"); +session_tmo_attr(idle_timeout, "%d"); +session_tmo_attr(ping_timeout, "%d"); +session_tmo_attr(abort_timeout, "%d"); +session_tmo_attr(reset_timeout, "%d"); + +static ssize_t +store_replacement_timeout(struct class_device *class_dev, const char *buf, + size_t count) +{ + struct Scsi_Host *shost = class_to_shost(class_dev); + struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; + int timeout; + + sscanf(buf, "%d\n", &timeout); + iscsi_update_replacement_timeout(session, timeout); + return count; +} + +session_show_function(replacement_timeout, "%d"); + +static CLASS_DEVICE_ATTR(connfail_timeout, S_IRUGO | S_IWUSR, + show_replacement_timeout, store_replacement_timeout); + + +#define session_show_time_fn(field, format_string) \ +static ssize_t \ +show_##field (struct class_device *class_dev, char *buf) \ +{ \ + struct Scsi_Host *shost = class_to_shost(class_dev); \ + struct iscsi_session *session; \ + session = (struct iscsi_session *)shost->hostdata; \ + return snprintf(buf, 20, format_string, \ + (jiffies - session->field) / HZ); \ +} + +#define session_rd_time_attr(field, format_string) \ + session_show_time_fn(field, format_string) \ +static CLASS_DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); + +session_rd_time_attr(session_established_time, "%lu"); +session_rd_time_attr(session_drop_time, "%lu"); + +struct class_device_attribute *iscsi_host_attrs[] = { + &class_device_attr_session_established, + &class_device_attr_shutdown, + &class_device_attr_drop_session, + &class_device_attr_connfail_timeout, + &class_device_attr_session_established_time, + &class_device_attr_session_drop_time, + &class_device_attr_login_timeout, + &class_device_attr_active_timeout, + &class_device_attr_idle_timeout, + &class_device_attr_ping_timeout, + &class_device_attr_abort_timeout, + &class_device_attr_reset_timeout, + &class_device_attr_window_closed, + NULL +}; + +static ssize_t iscsi_store_queue_depth(struct device *dev, const char *buf, + size_t count) +{ + struct scsi_device *sdev = to_scsi_device(dev); + int qdepth; + + if (!sdev->tagged_supported) + return count; + + if (sscanf(buf, "%10d\n", &qdepth) == 1 && + qdepth > 0 && qdepth <= ISCSI_MAX_CMDS_PER_LUN) + scsi_adjust_queue_depth(sdev, MSG_ORDERED_TAG, qdepth); + + return count; +} + +static DEVICE_ATTR(queue_depth, S_IWUSR, NULL, iscsi_store_queue_depth); + +struct device_attribute *iscsi_dev_attrs[] = { + &dev_attr_queue_depth, + NULL, +}; + +#define iscsi_transport_get_fn(field) \ +static void \ +iscsi_get_##field (struct scsi_target *stgt) \ +{ \ + struct Scsi_Host *shost = dev_to_shost(stgt->dev.parent); \ + struct iscsi_session *session; \ + session = (struct iscsi_session *)shost->hostdata; \ + iscsi_##field(stgt) = session->field; \ +} + +iscsi_transport_get_fn(tsih); +iscsi_transport_get_fn(initial_r2t); +iscsi_transport_get_fn(immediate_data); +iscsi_transport_get_fn(header_digest); +iscsi_transport_get_fn(data_digest); +iscsi_transport_get_fn(max_burst_len); +iscsi_transport_get_fn(first_burst_len); +iscsi_transport_get_fn(max_recv_data_segment_len); +iscsi_transport_get_fn(max_xmit_data_segment_len); + +#define iscsi_target_transport_cp_fn(field) \ +static ssize_t \ +iscsi_get_##field (struct scsi_target *stgt, char *buf, ssize_t count) \ +{ \ + struct Scsi_Host *shost = dev_to_shost(stgt->dev.parent); \ + struct iscsi_session *session; \ + session = (struct iscsi_session *)shost->hostdata; \ + return snprintf(buf, count - 1, "%s\n", session->field); \ +} + +iscsi_target_transport_cp_fn(target_name); +iscsi_target_transport_cp_fn(target_alias); + +static void +iscsi_get_ip_address(struct scsi_target *starget) +{ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); + struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; + struct sockaddr_in *addr = (struct sockaddr_in *)&session->addr; + /* + * I am pretty sure I messed up the socket data structure + * for ipv6 support. For now just do ipv4 until I can test + */ + iscsi_addr_type(starget) = addr->sin_family; + memcpy(&iscsi_sin_addr(starget), &addr->sin_addr, + sizeof(struct in_addr)); +} + +static void +iscsi_get_port(struct scsi_target *starget) +{ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); + struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; + + struct sockaddr_in *addr = (struct sockaddr_in *)&session->addr; + iscsi_port(starget) = addr->sin_port; +} + +static void +iscsi_get_tpgt(struct scsi_target *starget) +{ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); + struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; + + iscsi_tpgt(starget) = session->portal_group_tag; +} + +static void +iscsi_get_isid(struct scsi_target *starget) +{ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); + struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; + memcpy(iscsi_isid(starget), session->isid, sizeof(session->isid)); +} + +#define iscsi_host_transport_cp_fn(field) \ +static ssize_t \ +iscsi_get_##field (struct Scsi_Host *shost, char *buf, ssize_t count) \ +{ \ + struct iscsi_session *s = (struct iscsi_session *)shost->hostdata; \ + return snprintf(buf, count - 1, "%s\n", s->field); \ +} + +iscsi_host_transport_cp_fn(initiator_name); +iscsi_host_transport_cp_fn(initiator_alias); + +struct iscsi_function_template iscsi_fnt = { + .get_isid = iscsi_get_isid, + .show_isid = 1, + .get_tsih = iscsi_get_tsih, + .show_tsih = 1, + .get_port = iscsi_get_port, + .show_port = 1, + .get_tpgt = iscsi_get_tpgt, + .show_tpgt = 1, + .get_ip_address = iscsi_get_ip_address, + .show_ip_address = 1, + .get_initial_r2t = iscsi_get_initial_r2t, + .show_initial_r2t = 1, + .get_immediate_data = iscsi_get_immediate_data, + .show_immediate_data = 1, + .get_header_digest = iscsi_get_header_digest, + .show_header_digest = 1, + .get_data_digest = iscsi_get_data_digest, + .show_data_digest = 1, + .get_max_burst_len = iscsi_get_max_burst_len, + .show_max_burst_len = 1, + .get_first_burst_len = iscsi_get_first_burst_len, + .show_first_burst_len = 1, + .get_max_recv_data_segment_len = iscsi_get_max_recv_data_segment_len, + .show_max_recv_data_segment_len = 1, + .get_max_xmit_data_segment_len = iscsi_get_max_xmit_data_segment_len, + .show_max_xmit_data_segment_len = 1, + .get_target_name = iscsi_get_target_name, + .show_target_name = 1, + .get_target_alias = iscsi_get_target_alias, + .show_target_alias = 1, + .get_initiator_alias = iscsi_get_initiator_alias, + .show_initiator_alias = 1, + .get_initiator_name = iscsi_get_initiator_name, + .show_initiator_name = 1, +}; diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-auth.c linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-auth.c --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-auth.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-auth.c 2005-06-15 17:18:33.387472100 -0500 @@ -0,0 +1,144 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * $Id: iscsi-auth.c,v 1.1.2.5 2005/03/20 03:13:21 wysochanski Exp $ + * + * This file contains kernel wrappers around the iscsi auth common code. + */ +#include +#include +#include +#include + +#include "iscsi-sfnet.h" +#include "iscsi-protocol.h" +#include "iscsi-session.h" +/* + * Authenticate a target's CHAP response. + * + * Use the kernel crypto API + */ + +enum auth_dbg_status +acl_chap_compute_rsp(struct iscsi_acl *client, int rmt_auth, u32 id, + u8 *challenge_data, u32 challenge_length, + u8 *response_data) +{ + struct iscsi_session *session = client->session_handle; + u8 id_data[1]; + struct scatterlist sg; + struct crypto_tfm *tfm = session->md5_tfm; + u8 out_data[AUTH_STR_MAX_LEN]; + u32 out_length = AUTH_STR_MAX_LEN; + + if (!client->passwd_present) + return AUTH_DBG_STATUS_LOCAL_PASSWD_NOT_SET; + + crypto_digest_init(tfm); + /* id byte */ + id_data[0] = id; + sg_init_one(&sg, &id_data[0], 1); + crypto_digest_update(tfm, &sg, 1); + + /* decrypt password */ + if (acl_data(out_data, &out_length, client->passwd_data, + client->passwd_length)) + return AUTH_DBG_STATUS_PASSWD_DECRYPT_FAILED; + + if (!rmt_auth && !client->ip_sec && out_length < 12) + return AUTH_DBG_STATUS_PASSWD_TOO_SHORT_WITH_NO_IPSEC; + + /* shared secret */ + sg_init_one(&sg, out_data, out_length); + crypto_digest_update(tfm, &sg, 1); + + /* clear decrypted password */ + memset(out_data, 0, AUTH_STR_MAX_LEN); + + /* challenge value */ + sg_init_one(&sg, challenge_data, challenge_length); + crypto_digest_update(tfm, &sg, 1); + crypto_digest_final(tfm, response_data); + + return AUTH_DBG_STATUS_NOT_SET; /* no error */ +} + +int +acl_chap_auth_request(struct iscsi_acl *client, char *username, unsigned int id, + unsigned char *challenge_data, + unsigned int challenge_length, + unsigned char *response_data, + unsigned int rsp_length) +{ + struct iscsi_session *session = client->session_handle; + struct crypto_tfm *tfm = session->md5_tfm; + struct scatterlist sg[3]; + unsigned char id_byte = id; + unsigned char verify_data[16]; + + /* the expected credentials are in the session */ + if (session->username_in == NULL) { + iscsi_err("Failing authentication, no incoming username " + "configured to authenticate target %s\n", + session->target_name); + return AUTH_STATUS_FAIL; + } + if (strcmp(username, session->username_in) != 0) { + iscsi_err("Failing authentication, received incorrect username " + "from target %s\n", session->target_name); + return AUTH_STATUS_FAIL; + } + + if ((session->password_length_in < 1) || + (session->password_in == NULL) || + (session->password_in[0] == '\0')) { + iscsi_err("Failing authentication, no incoming password " + "configured to authenticate target %s\n", + session->target_name); + return AUTH_STATUS_FAIL; + } + + /* challenge length is I->T, and shouldn't need to be checked */ + + if (rsp_length != sizeof(verify_data)) { + iscsi_err("Failing authentication, received incorrect CHAP " + "response length %u from target %s\n", rsp_length, + session->target_name); + return AUTH_STATUS_FAIL; + } + + /* id byte */ + id_byte = id; + sg_init_one(&sg[0], &id_byte, 1); + + /* shared secret */ + sg_init_one(&sg[1], session->password_in, session->password_length_in); + + /* challenge value */ + sg_init_one(&sg[2], challenge_data, challenge_length); + + memset(verify_data, 0, sizeof(verify_data)); + crypto_digest_init(tfm); + crypto_digest_digest(tfm, sg, 3, verify_data); + + if (memcmp(response_data, verify_data, sizeof(verify_data)) == 0) + return AUTH_STATUS_PASS; + + iscsi_err("Failing authentication, received incorrect CHAP response " + "from target %s\n", session->target_name); + + return AUTH_STATUS_FAIL; +} diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-auth-client.c linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-auth-client.c --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-auth-client.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-auth-client.c 2005-06-15 17:18:53.019725499 -0500 @@ -0,0 +1,1841 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-auth-client.c,v 1.1.2.4 2005/03/15 06:33:38 wysochanski Exp $ + * + * This file implements the iSCSI CHAP authentication method based on + * RFC 3720. The code in this file is meant to be common for both kernel and + * user level and makes use of only limited library functions, presently only + * string.h. Routines specific to kernel, user level are implemented in + * seperate files under the appropriate directories. + * This code in this files assumes a single thread of execution + * for each iscsi_acl structure, and does no locking. + */ +#include "iscsi-auth-client.h" +#include "iscsi-session.h" +#include "iscsi-protocol.h" +#include "iscsi-sfnet.h" + +static const char acl_hexstring[] = "0123456789abcdefABCDEF"; +static const char acl_base64_string[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char acl_authmethod_set_chap_alg_list[] = "CHAP"; +static const char acl_reject_option_name[] = "Reject"; +static const char acl_none_option_name[] = "None"; + +static int +acl_text_to_number(const char *text, unsigned long *num) +{ + char *end; + unsigned long number = *num; + + if (text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) + number = simple_strtoul(text + 2, &end, 16); + else + number = simple_strtoul(text, &end, 10); + + if (*text != '\0' && *end == '\0') { + *num = number; + return 0; /* No error */ + } else + return 1; /* Error */ +} + +static int +acl_chk_string(const char *s, unsigned int max_len, unsigned int *out_len) +{ + unsigned int len; + + if (!s) + return 1; + + for (len = 0; len < max_len; len++) + if (*s++ == '\0') { + if (out_len) + *out_len = len; + return 0; + } + + return 1; +} + +static int +acl_str_index(const char *s, int c) +{ + char *str = strchr(s, c); + + if (str) + return (str - s); + else + return -1; +} + +static int +acl_chk_auth_mthd_optn(int val) +{ + if (val == AUTH_OPTION_NONE || val == AUTH_METHOD_CHAP) + return 0; + + return 1; +} + +static const char * +acl_authmethod_optn_to_text(int value) +{ + const char *s; + switch (value) { + case AUTH_OPTION_REJECT: + s = acl_reject_option_name; + break; + case AUTH_OPTION_NONE: + s = acl_none_option_name; + break; + case AUTH_METHOD_CHAP: + s = acl_authmethod_set_chap_alg_list; + break; + default: + s = 0; + } + return s; +} + +static int +acl_chk_chap_alg_optn(int chap_algorithm) +{ + if (chap_algorithm == AUTH_OPTION_NONE || + chap_algorithm == AUTH_CHAP_ALG_MD5) + return 0; + + return 1; +} + +static int +acl_data_to_text(unsigned char *data, unsigned int data_length, char *text, + unsigned int text_length) +{ + unsigned long n; + + if (!text || text_length == 0) + return 1; + + if (!data || data_length == 0) { + *text = '\0'; + return 1; + } + + if (text_length < 3) { + *text = '\0'; + return 1; + } + + *text++ = '0'; + *text++ = 'x'; + + text_length -= 2; + + while (data_length > 0) { + + if (text_length < 3) { + *text = '\0'; + return 1; + } + + n = *data++; + data_length--; + + *text++ = acl_hexstring[(n >> 4) & 0xf]; + *text++ = acl_hexstring[n & 0xf]; + + text_length -= 2; + } + + *text = '\0'; + + return 0; +} + +static int +acl_hex_to_data(const char *text, unsigned int text_length, unsigned char *data, + unsigned int *data_lenp) +{ + int i; + unsigned int n1; + unsigned int n2; + unsigned int data_length = *data_lenp; + + if ((text_length % 2) == 1) { + + i = acl_str_index(acl_hexstring, *text++); + if (i < 0) + return 1; /* error, bad character */ + + if (i > 15) + i -= 6; + n2 = i; + + if (data_length < 1) + return 1; /* error, too much data */ + + *data++ = n2; + data_length--; + } + + while (*text != '\0') { + i = acl_str_index(acl_hexstring, *text++); + if (i < 0) + return 1; /* error, bad character */ + + if (i > 15) + i -= 6; + n1 = i; + + if (*text == '\0') + return 1; /* error, odd string length */ + + i = acl_str_index(acl_hexstring, *text++); + if (i < 0) + return 1; /* error, bad character */ + + if (i > 15) + i -= 6; + n2 = i; + + if (data_length < 1) + return 1; /* error, too much data */ + + *data++ = (n1 << 4) | n2; + data_length--; + } + + if (data_length >= *data_lenp) + return 1; /* error, no data */ + + *data_lenp = *data_lenp - data_length; + + return 0; /* no error */ +} + +static int +acl_base64_to_data(const char *text, unsigned char *data, + unsigned int *data_lenp) +{ + int i; + unsigned int n; + unsigned int count; + unsigned int data_length = *data_lenp; + + n = 0; + count = 0; + + while (*text != '\0' && *text != '=') { + + i = acl_str_index(acl_base64_string, *text++); + if (i < 0) + return 1; /* error, bad character */ + + n = (n << 6 | (unsigned int)i); + count++; + + if (count >= 4) { + if (data_length < 3) + return 1; /* error, too much data */ + *data++ = n >> 16; + *data++ = n >> 8; + *data++ = n; + data_length -= 3; + n = 0; + count = 0; + } + } + + while (*text != '\0') + if (*text++ != '=') + return 1; /* error, bad pad */ + + if (count == 0) { + /* do nothing */ + } else if (count == 2) { + if (data_length < 1) + return 1; /* error, too much data */ + n = n >> 4; + *data++ = n; + data_length--; + } else if (count == 3) { + if (data_length < 2) + return 1; /* error, too much data */ + n = n >> 2; + *data++ = n >> 8; + *data++ = n; + data_length -= 2; + } else + return 1; /* bad encoding */ + + if (data_length >= *data_lenp) + return 1; /* error, no data */ + + *data_lenp = *data_lenp - data_length; + + return 0; /* no error */ +} + +static int +acl_text_to_data(const char *text, unsigned char *data, + unsigned int *data_length) +{ + int status; + unsigned int text_length; + + status = acl_chk_string(text, 2 + 2 * AUTH_LARGE_BINARY_MAX_LEN + 1, + &text_length); + if (status) + return status; + + if (text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) { + /* skip prefix */ + text += 2; + text_length -= 2; + status = acl_hex_to_data(text, text_length, data, data_length); + } else if (text[0] == '0' && (text[1] == 'b' || text[1] == 'B')) { + /* skip prefix */ + text += 2; + text_length -= 2; + status = acl_base64_to_data(text, data, data_length); + } else + status = 1; /* prefix not recognized. */ + + return status; +} + +static void +acl_init_key_blk(struct auth_key_block *key_blk) +{ + char *str_block = key_blk->str_block; + + memset(key_blk, 0, sizeof(*key_blk)); + key_blk->str_block = str_block; +} + +static void +acl_set_key_value(struct auth_key_block *key_blk, int key_type, + const char *key_val) +{ + unsigned int length; + char *string; + + if (key_blk->key[key_type].value_set) { + key_blk->dup_set = 1; + return; + } + + key_blk->key[key_type].value_set = 1; + + if (!key_val) + return; + + if (acl_chk_string(key_val, AUTH_STR_MAX_LEN, &length)) { + key_blk->str_too_long = 1; + return; + } + + length += 1; + + if ((key_blk->blk_length + length) > AUTH_STR_BLOCK_MAX_LEN) { + key_blk->too_much_data = 1; + return; + } + + string = &key_blk->str_block[key_blk->blk_length]; + + if (strlcpy(string, key_val, length) >= length) { + key_blk->too_much_data = 1; + return; + } + key_blk->blk_length += length; + + key_blk->key[key_type].string = string; + key_blk->key[key_type].present = 1; +} + +static const char * +acl_get_key_val(struct auth_key_block *key_blk, int key_type) +{ + key_blk->key[key_type].processed = 1; + + if (!key_blk->key[key_type].present) + return 0; + + return key_blk->key[key_type].string; +} + +static void +acl_chk_key(struct iscsi_acl *client, int key_type, int *negotiated_option, + unsigned int option_count, int *option_list, + const char *(*value_to_text) (int)) +{ + const char *key_val; + int length; + unsigned int i; + + key_val = acl_get_key_val(&client->recv_key_block, key_type); + if (!key_val) { + *negotiated_option = AUTH_OPTION_NOT_PRESENT; + return; + } + + while (*key_val != '\0') { + + length = 0; + + while (*key_val != '\0' && *key_val != ',') + client->scratch_key_value[length++] = *key_val++; + + if (*key_val == ',') + key_val++; + client->scratch_key_value[length++] = '\0'; + + for (i = 0; i < option_count; i++) { + const char *s = (*value_to_text)(option_list[i]); + + if (!s) + continue; + + if (strcmp(client->scratch_key_value, s) == 0) { + *negotiated_option = option_list[i]; + return; + } + } + } + + *negotiated_option = AUTH_OPTION_REJECT; +} + +static void +acl_set_key(struct iscsi_acl *client, int key_type, unsigned int option_count, + int *option_list, const char *(*value_to_text)(int)) +{ + unsigned int i; + + if (option_count == 0) { + /* + * No valid options to send, but we always want to + * send something. + */ + acl_set_key_value(&client->send_key_block, key_type, + acl_none_option_name); + return; + } + + if (option_count == 1 && option_list[0] == AUTH_OPTION_NOT_PRESENT) { + acl_set_key_value(&client->send_key_block, key_type, 0); + return; + } + + for (i = 0; i < option_count; i++) { + const char *s = (*value_to_text)(option_list[i]); + + if (!s) + continue; + + if (i == 0) + strlcpy(client->scratch_key_value, s, + AUTH_STR_MAX_LEN); + else { + strlcat(client->scratch_key_value, ",", + AUTH_STR_MAX_LEN); + strlcat(client->scratch_key_value, s, + AUTH_STR_MAX_LEN); + } + } + + acl_set_key_value(&client->send_key_block, key_type, + client->scratch_key_value); +} + +static void +acl_chk_auth_method_key(struct iscsi_acl *client) +{ + acl_chk_key(client, AUTH_KEY_TYPE_AUTH_METHOD, + &client->negotiated_auth_method, + client->auth_method_valid_count, + client->auth_method_valid_list, + acl_authmethod_optn_to_text); +} + +static void +acl_set_auth_method_key(struct iscsi_acl *client, + unsigned int auth_method_count, int *auth_method_list) +{ + acl_set_key(client, AUTH_KEY_TYPE_AUTH_METHOD, auth_method_count, + auth_method_list, acl_authmethod_optn_to_text); +} + +static void +acl_chk_chap_alg_key(struct iscsi_acl *client) +{ + const char *key_val; + int length; + unsigned long number; + unsigned int i; + + key_val = acl_get_key_val(&client->recv_key_block, + AUTH_KEY_TYPE_CHAP_ALG); + if (!key_val) { + client->negotiated_chap_alg = AUTH_OPTION_NOT_PRESENT; + return; + } + + while (*key_val != '\0') { + + length = 0; + + while (*key_val != '\0' && *key_val != ',') + client->scratch_key_value[length++] = *key_val++; + + if (*key_val == ',') + key_val++; + client->scratch_key_value[length++] = '\0'; + + if (acl_text_to_number(client->scratch_key_value, &number)) + continue; + + + for (i = 0; i < client->chap_alg_count; i++) + if (number == (unsigned long)client->chap_alg_list[i]) + { + client->negotiated_chap_alg = number; + return; + } + } + + client->negotiated_chap_alg = AUTH_OPTION_REJECT; +} + +static void +acl_set_chap_alg_key(struct iscsi_acl *client, unsigned int chap_alg_count, + int *chap_alg_list) +{ + unsigned int i; + + if (chap_alg_count == 0) { + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_ALG, 0); + return; + } + + if (chap_alg_count == 1 && + chap_alg_list[0] == AUTH_OPTION_NOT_PRESENT) { + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_ALG, 0); + return; + } + + if (chap_alg_count == 1 && chap_alg_list[0] == AUTH_OPTION_REJECT) { + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_ALG, + acl_reject_option_name); + return; + } + + for (i = 0; i < chap_alg_count; i++) { + char s[20]; + + snprintf(s, sizeof(s), "%lu",(unsigned long)chap_alg_list[i]); + + if (i == 0) + strlcpy(client->scratch_key_value, s, + AUTH_STR_MAX_LEN); + else { + strlcat(client->scratch_key_value, ",", + AUTH_STR_MAX_LEN); + strlcat(client->scratch_key_value, s, + AUTH_STR_MAX_LEN); + } + } + + acl_set_key_value(&client->send_key_block, AUTH_KEY_TYPE_CHAP_ALG, + client->scratch_key_value); +} + +static void +acl_next_phase(struct iscsi_acl *client) +{ + switch (client->phase) { + case AUTH_PHASE_CONFIGURE: + client->phase = AUTH_PHASE_NEGOTIATE; + break; + case AUTH_PHASE_NEGOTIATE: + client->phase = AUTH_PHASE_AUTHENTICATE; + + if (client->negotiated_auth_method == AUTH_OPTION_REJECT || + client->negotiated_auth_method == AUTH_OPTION_NOT_PRESENT || + client->negotiated_auth_method == AUTH_OPTION_NONE) { + + client->local_state = AUTH_LOCAL_STATE_DONE; + client->rmt_state = AUTH_RMT_STATE_DONE; + + if (client->auth_rmt) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + } else + client->rmt_auth_status = AUTH_STATUS_PASS; + + switch (client->negotiated_auth_method) { + case AUTH_OPTION_REJECT: + client->dbg_status = + AUTH_DBG_STATUS_AUTH_METHOD_REJECT; + break; + case AUTH_OPTION_NOT_PRESENT: + client->dbg_status = + AUTH_DBG_STATUS_AUTH_METHOD_NOT_PRESENT; + break; + case AUTH_OPTION_NONE: + client->dbg_status = + AUTH_DBG_STATUS_AUTH_METHOD_NONE; + } + + } else if (client->negotiated_auth_method == AUTH_METHOD_CHAP) { + client->local_state = AUTH_LOCAL_STATE_SEND_ALG; + client->rmt_state = AUTH_RMT_STATE_SEND_ALG; + } else { + + client->local_state = AUTH_LOCAL_STATE_DONE; + client->rmt_state = AUTH_RMT_STATE_DONE; + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->dbg_status = AUTH_DBG_STATUS_AUTH_METHOD_BAD; + } + break; + case AUTH_PHASE_AUTHENTICATE: + client->phase = AUTH_PHASE_DONE; + break; + case AUTH_PHASE_DONE: + case AUTH_PHASE_ERROR: + default: + client->phase = AUTH_PHASE_ERROR; + } +} + +static void +acl_local_auth(struct iscsi_acl *client) +{ + unsigned int chap_identifier; + unsigned char response_data[AUTH_CHAP_RSP_LEN]; + unsigned long number; + int status; + enum auth_dbg_status dbg_status; + const char *chap_identifier_key_val; + const char *chap_challenge_key_val; + + switch (client->local_state) { + case AUTH_LOCAL_STATE_SEND_ALG: + if (client->node_type == TYPE_INITIATOR) { + acl_set_chap_alg_key(client, client->chap_alg_count, + client->chap_alg_list); + client->local_state = AUTH_LOCAL_STATE_RECV_ALG; + break; + } + /* Fall through */ + case AUTH_LOCAL_STATE_RECV_ALG: + acl_chk_chap_alg_key(client); + + if (client->node_type == TYPE_TARGET) + acl_set_chap_alg_key(client, 1, + &client->negotiated_chap_alg); + + /* Make sure only supported CHAP algorithm is used. */ + if (client->negotiated_chap_alg == AUTH_OPTION_NOT_PRESENT) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_ALG_EXPECTED; + break; + } else if (client->negotiated_chap_alg == AUTH_OPTION_REJECT) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_ALG_REJECT; + break; + } else if (client->negotiated_chap_alg != AUTH_CHAP_ALG_MD5) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_ALG_BAD; + break; + } + if (client->node_type == TYPE_TARGET) { + client->local_state = AUTH_LOCAL_STATE_RECV_CHALLENGE; + break; + } + /* Fall through */ + case AUTH_LOCAL_STATE_RECV_CHALLENGE: + chap_identifier_key_val = acl_get_key_val(&client->recv_key_block, + AUTH_KEY_TYPE_CHAP_IDENTIFIER); + chap_challenge_key_val = acl_get_key_val(&client->recv_key_block, + AUTH_KEY_TYPE_CHAP_CHALLENGE); + if (client->node_type == TYPE_TARGET) { + if (!chap_identifier_key_val && + !chap_challenge_key_val) { + client->local_state = AUTH_LOCAL_STATE_DONE; + break; + } + } + + if (!chap_identifier_key_val) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = + AUTH_DBG_STATUS_CHAP_IDENTIFIER_EXPECTED; + break; + } + + if (!chap_challenge_key_val) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = + AUTH_DBG_STATUS_CHAP_CHALLENGE_EXPECTED; + break; + } + + status = acl_text_to_number(chap_identifier_key_val, &number); + if (status || (255 < number)) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_IDENTIFIER_BAD; + break; + } + chap_identifier = number; + + if (client->recv_chap_challenge_status) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHALLENGE_BAD; + break; + } + + if (client->node_type == TYPE_TARGET && + client->recv_chap_challenge.length == + client->send_chap_challenge.length && + memcmp(client->recv_chap_challenge.large_binary, + client->send_chap_challenge.large_binary, + client->send_chap_challenge.length) == 0) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = + AUTH_DBG_STATUS_CHAP_CHALLENGE_REFLECTED; + break; + } + + dbg_status = acl_chap_compute_rsp(client, 0, + chap_identifier, + client->recv_chap_challenge.large_binary, + client->recv_chap_challenge.length, + response_data); + + if (dbg_status != AUTH_DBG_STATUS_NOT_SET) { + client->local_state = AUTH_LOCAL_STATE_ERROR; + client->dbg_status = dbg_status; + break; + } + + acl_data_to_text(response_data, + AUTH_CHAP_RSP_LEN, client->scratch_key_value, + AUTH_STR_MAX_LEN); + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_RSP, + client->scratch_key_value); + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_USERNAME, + client->username); + + client->local_state = AUTH_LOCAL_STATE_DONE; + break; + case AUTH_LOCAL_STATE_DONE: + break; + case AUTH_LOCAL_STATE_ERROR: + default: + client->phase = AUTH_PHASE_ERROR; + } +} + +static void +acl_rmt_auth(struct iscsi_acl *client) +{ + unsigned char id_data[1]; + unsigned char response_data[AUTH_STR_MAX_LEN]; + unsigned int rsp_len = AUTH_STR_MAX_LEN; + unsigned char my_rsp_data[AUTH_CHAP_RSP_LEN]; + int status; + enum auth_dbg_status dbg_status; + const char *chap_rsp_key_val; + const char *chap_username_key_val; + + switch (client->rmt_state) { + case AUTH_RMT_STATE_SEND_ALG: + if (client->node_type == TYPE_INITIATOR) { + client->rmt_state = AUTH_RMT_STATE_SEND_CHALLENGE; + break; + } + /* Fall through */ + case AUTH_RMT_STATE_SEND_CHALLENGE: + if (!client->auth_rmt) { + client->rmt_auth_status = AUTH_STATUS_PASS; + client->dbg_status = AUTH_DBG_STATUS_AUTH_RMT_FALSE; + client->rmt_state = AUTH_RMT_STATE_DONE; + break; + } + get_random_bytes(id_data, 1); + client->send_chap_identifier = id_data[0]; + snprintf(client->scratch_key_value, AUTH_STR_MAX_LEN, "%lu", + (unsigned long)client->send_chap_identifier); + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_IDENTIFIER, + client->scratch_key_value); + + client->send_chap_challenge.length = client->chap_challenge_len; + get_random_bytes(client->send_chap_challenge.large_binary, + client->send_chap_challenge.length); + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_CHALLENGE, ""); + + client->rmt_state = AUTH_RMT_STATE_RECV_RSP; + break; + case AUTH_RMT_STATE_RECV_RSP: + chap_rsp_key_val = acl_get_key_val(&client->recv_key_block, + AUTH_KEY_TYPE_CHAP_RSP); + chap_username_key_val = acl_get_key_val(&client->recv_key_block, + AUTH_KEY_TYPE_CHAP_USERNAME); + + if (!chap_rsp_key_val) { + client->rmt_state = AUTH_RMT_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_RSP_EXPECTED; + break; + } + + if (!chap_username_key_val) { + client->rmt_state = AUTH_RMT_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_USERNAME_EXPECTED; + break; + } + + status = acl_text_to_data(chap_rsp_key_val, response_data, + &rsp_len); + + if (status) { + client->rmt_state = AUTH_RMT_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_CHAP_RSP_BAD; + break; + } + + if (rsp_len == AUTH_CHAP_RSP_LEN) { + dbg_status = acl_chap_compute_rsp(client, 1, + client->send_chap_identifier, + client->send_chap_challenge.large_binary, + client->send_chap_challenge.length, + my_rsp_data); + + if (dbg_status == AUTH_DBG_STATUS_NOT_SET && + memcmp(my_rsp_data, response_data, + AUTH_CHAP_RSP_LEN) == 0) { + client->rmt_state = AUTH_RMT_STATE_ERROR; + client->dbg_status = AUTH_DBG_STATUS_PASSWD_IDENTICAL; + break; + } + } + + strlcpy(client->chap_username, chap_username_key_val, + AUTH_STR_MAX_LEN); + + status = acl_chap_auth_request(client, client->chap_username, + client->send_chap_identifier, + client->send_chap_challenge. + large_binary, + client->send_chap_challenge. + length, response_data, + rsp_len); + + client->rmt_auth_status = (enum auth_status) status; + client->auth_rsp_flag = 1; + + if (client->auth_server_error_flag) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->dbg_status = AUTH_DBG_STATUS_AUTH_SERVER_ERROR; + } else if (client->rmt_auth_status == AUTH_STATUS_PASS) + client->dbg_status = AUTH_DBG_STATUS_AUTH_PASS; + else if (client->rmt_auth_status == AUTH_STATUS_FAIL) + client->dbg_status = AUTH_DBG_STATUS_AUTH_FAIL; + else { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->dbg_status = AUTH_DBG_STATUS_AUTH_STATUS_BAD; + } + client->rmt_state = AUTH_RMT_STATE_DONE; + + /* Fall through */ + case AUTH_RMT_STATE_DONE: + break; + case AUTH_RMT_STATE_ERROR: + default: + client->phase = AUTH_PHASE_ERROR; + } +} + +static void +acl_hand_shake(struct iscsi_acl *client) +{ + if (client->phase == AUTH_PHASE_DONE) + + /* + * Should only happen if authentication + * protocol error occured. + */ + return; + + if (client->node_type == TYPE_INITIATOR) + + /* + * Target should only have set T bit on response if + * initiator set it on previous message. + */ + if (client->recv_key_block.transit_bit && + !client->transit_bit_sent_flag) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = + AUTH_DBG_STATUS_T_BIT_SET_ILLEGAL; + return; + } + + if (client->phase == AUTH_PHASE_NEGOTIATE) { + /* + * Should only happen if waiting for peer + * to send AuthMethod key or set Transit Bit. + */ + if (client->node_type == TYPE_INITIATOR) + client->send_key_block.transit_bit = 1; + return; + } + + if (client->rmt_state == AUTH_RMT_STATE_RECV_RSP || + client->rmt_state == AUTH_RMT_STATE_DONE) { + if (client->node_type == TYPE_INITIATOR) { + if (client->recv_key_block.transit_bit) { + if (client->rmt_state != + AUTH_RMT_STATE_DONE) + goto recv_transit_bit_err; + acl_next_phase(client); + } else + client->send_key_block.transit_bit = 1; + } else { + if (client->rmt_state == AUTH_RMT_STATE_DONE && + client->rmt_auth_status != AUTH_STATUS_PASS) + /* + * Authentication failed, don't do T bit + * handshake. + */ + acl_next_phase(client); + else { + /* + * Target can only set T bit on response if + * initiator set it on current message. + */ + if (client->recv_key_block.transit_bit) { + client->send_key_block.transit_bit = 1; + acl_next_phase(client); + } + } + } + } else + if (client->node_type == TYPE_INITIATOR) + if (client->recv_key_block.transit_bit) + goto recv_transit_bit_err; + return; + + recv_transit_bit_err: + /* + * Target set T bit on response but + * initiator was not done with authentication. + */ + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = AUTH_DBG_STATUS_T_BIT_SET_PREMATURE; +} + +static int +acl_rcv_end_status(struct iscsi_acl *client) +{ + int auth_status; + int key_type; + + if (client->phase == AUTH_PHASE_ERROR) + return AUTH_STATUS_ERROR; + + if (client->phase == AUTH_PHASE_DONE) { + + /* Perform sanity check against configured parameters. */ + if (client->auth_rmt && !client->auth_rsp_flag && + client->rmt_auth_status == AUTH_STATUS_PASS) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->dbg_status = AUTH_DBG_STATUS_AUTHPASS_NOT_VALID; + } + + auth_status = client->rmt_auth_status; + + } else + auth_status = AUTH_STATUS_CONTINUE; + + if (auth_status == AUTH_STATUS_CONTINUE || + auth_status == AUTH_STATUS_PASS) { + if (client->send_key_block.dup_set) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = + AUTH_DBG_STATUS_SEND_DUP_SET_KEY_VALUE; + auth_status = AUTH_STATUS_FAIL; + } else if (client->send_key_block.str_too_long) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = + AUTH_DBG_STATUS_SEND_STR_TOO_LONG; + auth_status = AUTH_STATUS_FAIL; + } else if (client->send_key_block.too_much_data) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = + AUTH_DBG_STATUS_SEND_TOO_MUCH_DATA; + auth_status = AUTH_STATUS_FAIL; + } else { + /* Check that all incoming keys have been processed. */ + + for (key_type = AUTH_KEY_TYPE_FIRST; + key_type < AUTH_KEY_TYPE_MAX_COUNT; key_type++) + if (client->recv_key_block.key[key_type].present && + !client->recv_key_block.key[key_type]. + processed) + break; + + if (key_type < AUTH_KEY_TYPE_MAX_COUNT) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = + AUTH_DBG_STATUS_UNEXPECTED_KEY_PRESENT; + auth_status = AUTH_STATUS_FAIL; + } + } + } + + if (auth_status != AUTH_STATUS_PASS && + auth_status != AUTH_STATUS_CONTINUE) { + int auth_method_key_present = 0; + int chap_alg_key_present = 0; + + /* + * Suppress send keys on error, + * except for AuthMethod and CHAP_A. + */ + if (client->node_type == TYPE_TARGET) { + if (acl_get_key_val(&client->send_key_block, + AUTH_KEY_TYPE_AUTH_METHOD)) + auth_method_key_present = 1; + else if (acl_get_key_val(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_ALG)) + chap_alg_key_present = 1; + } + + acl_init_key_blk(&client->send_key_block); + + if (client->node_type == TYPE_TARGET) { + if (auth_method_key_present && + client->negotiated_auth_method == + AUTH_OPTION_REJECT) + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_AUTH_METHOD, + acl_reject_option_name); + else if (chap_alg_key_present && + client->negotiated_chap_alg == + AUTH_OPTION_REJECT) + acl_set_key_value(&client->send_key_block, + AUTH_KEY_TYPE_CHAP_ALG, + acl_reject_option_name); + } + } + client->recv_in_progress_flag = 0; + + return auth_status; +} + +int +acl_recv_begin(struct iscsi_acl *client) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase == AUTH_PHASE_ERROR) + return AUTH_STATUS_ERROR; + + if (client->phase == AUTH_PHASE_DONE) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (client->recv_in_progress_flag) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + client->recv_in_progress_flag = 1; + + if (client->phase == AUTH_PHASE_CONFIGURE) + acl_next_phase(client); + + client->transit_bit_sent_flag = client->send_key_block.transit_bit; + + acl_init_key_blk(&client->recv_key_block); + acl_init_key_blk(&client->send_key_block); + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_recv_end(struct iscsi_acl *client) +{ + int next_phase_flag = 0; + + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase == AUTH_PHASE_ERROR) + return AUTH_STATUS_ERROR; + + if (!client->recv_in_progress_flag) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (client->recv_end_count > AUTH_RECV_END_MAX_COUNT) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = AUTH_DBG_STATUS_RECV_MSG_COUNT_LIMIT; + } else if (client->recv_key_block.dup_set) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = AUTH_DBG_STATUS_RECV_DUP_SET_KEY_VALUE; + } else if (client->recv_key_block.str_too_long) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = AUTH_DBG_STATUS_RECV_STR_TOO_LONG; + } else if (client->recv_key_block.too_much_data) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = AUTH_DBG_STATUS_RECV_TOO_MUCH_DATA; + } + + client->recv_end_count++; + + switch (client->phase) { + case AUTH_PHASE_NEGOTIATE: + acl_chk_auth_method_key(client); + if (client->auth_method_valid_neg_role == + AUTH_NEG_ROLE_RESPONDER) { + if (client->negotiated_auth_method == + AUTH_OPTION_NOT_PRESENT) { + if (client->auth_rmt || + !client->recv_key_block.transit_bit) { + /* + * No AuthMethod key from peer on + * first message, try moving the + * process along by sending the + * AuthMethod key. + */ + + client->auth_method_valid_neg_role = + AUTH_NEG_ROLE_ORIGINATOR; + acl_set_auth_method_key(client, + client->auth_method_valid_count, + client->auth_method_valid_list); + break; + } + + /* + * Special case if peer sent no AuthMethod key, + * but did set Transit Bit, allowing this side + * to do a null authentication, and compelete + * the iSCSI security phase without either side + * sending the AuthMethod key. + */ + } else + /* Send response to AuthMethod key. */ + acl_set_auth_method_key(client, 1, + &client->negotiated_auth_method); + + if (client->node_type == TYPE_INITIATOR) + acl_next_phase(client); + else + next_phase_flag = 1; + } else { + + if (client->negotiated_auth_method == + AUTH_OPTION_NOT_PRESENT) { + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + client->dbg_status = + AUTH_DBG_STATUS_AUTH_METHOD_EXPECTED; + break; + } + + acl_next_phase(client); + } + break; + case AUTH_PHASE_AUTHENTICATE: + case AUTH_PHASE_DONE: + break; + default: + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + switch (client->phase) { + case AUTH_PHASE_NEGOTIATE: + if (next_phase_flag) + acl_next_phase(client); + break; + case AUTH_PHASE_AUTHENTICATE: + /* + * Must call acl_local_auth() + * before acl_rmt_auth() + * to insure processing of the CHAP algorithm key, + * and to avoid leaving an in progress request to the + * authentication service. + */ + acl_local_auth(client); + + if (client->local_state != AUTH_LOCAL_STATE_ERROR) + acl_rmt_auth(client); + + if (client->local_state == AUTH_LOCAL_STATE_ERROR || + client->rmt_state == AUTH_RMT_STATE_ERROR) { + + client->rmt_auth_status = AUTH_STATUS_FAIL; + client->phase = AUTH_PHASE_DONE; + /* client->dbg_status should already be set. */ + } + break; + case AUTH_PHASE_DONE: + break; + default: + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + acl_hand_shake(client); + + return acl_rcv_end_status(client); +} + +const char * +acl_get_key_name(int key_type) +{ + /* + * Note: The ordering of this table must match the order + * defined by enum auth_key_type in iscsi-auth-client.h. + */ + static char *const key_names[AUTH_KEY_TYPE_MAX_COUNT] = { + "AuthMethod", + "CHAP_A", + "CHAP_N", + "CHAP_R", + "CHAP_I", + "CHAP_C" + }; + + if (key_type < AUTH_KEY_TYPE_FIRST || key_type > AUTH_KEY_TYPE_LAST) + return 0; + + return key_names[key_type]; +} + +int +acl_get_next_key_type(int *key_type) +{ + if (*key_type >= AUTH_KEY_TYPE_LAST) + return AUTH_STATUS_ERROR; + + if (*key_type < AUTH_KEY_TYPE_FIRST) + *key_type = AUTH_KEY_TYPE_FIRST; + else + (*key_type)++; + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_recv_key_value(struct iscsi_acl *client, int key_type, + const char *user_key_val) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_NEGOTIATE && + client->phase != AUTH_PHASE_AUTHENTICATE) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (key_type < AUTH_KEY_TYPE_FIRST || key_type > AUTH_KEY_TYPE_LAST) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (key_type == AUTH_KEY_TYPE_CHAP_CHALLENGE) { + client->recv_chap_challenge.length = + AUTH_LARGE_BINARY_MAX_LEN; + client->recv_chap_challenge_status = + acl_text_to_data(user_key_val, + client->recv_chap_challenge.large_binary, + &client->recv_chap_challenge.length); + user_key_val = ""; + } + + acl_set_key_value(&client->recv_key_block, key_type, user_key_val); + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_send_key_val(struct iscsi_acl *client, int key_type, int *key_present, + char *user_key_val, unsigned int max_length) +{ + const char *key_val; + + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE && + client->phase != AUTH_PHASE_NEGOTIATE && + client->phase != AUTH_PHASE_AUTHENTICATE && + client->phase != AUTH_PHASE_DONE) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (key_type < AUTH_KEY_TYPE_FIRST || key_type > AUTH_KEY_TYPE_LAST) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + key_val = acl_get_key_val(&client->send_key_block, key_type); + if (key_val) { + if (key_type == AUTH_KEY_TYPE_CHAP_CHALLENGE) { + if (acl_data_to_text(client->send_chap_challenge.large_binary, + client->send_chap_challenge.length, user_key_val, + max_length)) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + } else if (strlcpy(user_key_val, key_val, max_length) >= + max_length) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + *key_present = 1; + } else + *key_present = 0; + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_recv_transit_bit(struct iscsi_acl *client, int value) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_NEGOTIATE && + client->phase != AUTH_PHASE_AUTHENTICATE) { + + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (value) + client->recv_key_block.transit_bit = 1; + else + client->recv_key_block.transit_bit = 0; + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_send_transit_bit(struct iscsi_acl *client, int *value) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE && + client->phase != AUTH_PHASE_NEGOTIATE && + client->phase != AUTH_PHASE_AUTHENTICATE && + client->phase != AUTH_PHASE_DONE) { + + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + *value = client->send_key_block.transit_bit; + + return AUTH_STATUS_NO_ERROR; +} + +static int +acl_set_option_list(struct iscsi_acl *client, unsigned int opt_count, + const int *opt_list, unsigned int *clnt_optn_count, + int *clnt_optn_list, unsigned int optn_max_count, + int (*chk_option)(int), + int (*chk_list)(unsigned int opt_count, const int *opt_list)) +{ + unsigned int i, j; + + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE || + opt_count > optn_max_count) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + for (i = 0; i < opt_count; i++) + if (chk_option(opt_list[i])) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + /* Check for duplicate entries. */ + for (i = 0; i < opt_count; i++) + for (j = 0; j < opt_count; j++) { + if (j == i) + continue; + if (opt_list[i] == opt_list[j]) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + } + + /* Check for key specific constraints. */ + if (chk_list) + if (chk_list(opt_count, opt_list)) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + for (i = 0; i < opt_count; i++) + clnt_optn_list[i] = opt_list[i]; + + *clnt_optn_count = opt_count; + + return AUTH_STATUS_NO_ERROR; +} + +static int +acl_chk_auth_method_list(unsigned int option_count, const int *option_list) +{ + unsigned int i; + + if (!option_list || option_count < 2) + return 1; + + if (option_list[option_count - 1] != AUTH_OPTION_NONE) + return 1; + + for (i = 0; i < (option_count - 1); i++) + if (option_list[i] != AUTH_OPTION_NONE) + return 0; + + return 0; +} + +static void +acl_set_auth_method_valid(struct iscsi_acl *client) +{ + unsigned int i, j = 0; + int option = 0; + + /* + * Following checks may need to be revised if + * authentication options other than CHAP and none + * are supported. + */ + if (client->node_type == TYPE_INITIATOR) { + if (client->auth_rmt) + /* + * If initiator doing authentication, + * don't offer authentication option none. + */ + option = 1; + else if (!client->passwd_present) + /* + * If initiator password not set, + * only offer authentication option none. + */ + option = 2; + } + + if (client->node_type == TYPE_TARGET) { + if (client->auth_rmt) + /* + * If target doing authentication, + * don't accept authentication option none. + */ + option = 1; + else + /* + * If target not doing authentication, + * only accept authentication option none. + */ + option = 2; + } + + for (i = 0; i < client->auth_method_count; i++) { + if (option == 1) { + if (client->auth_method_list[i] == AUTH_OPTION_NONE) + continue; + } else if (option == 2) + if (client->auth_method_list[i] != AUTH_OPTION_NONE) + continue; + client->auth_method_valid_list[j++] = client->auth_method_list[i]; + } + + client->auth_method_valid_count = j; + + acl_init_key_blk(&client->send_key_block); + + if (client->node_type == TYPE_INITIATOR) { + if (client->auth_rmt) { + /* + * Initiator wants to authenticate target, + * always send AuthMethod key. + */ + client->send_key_block.transit_bit = 0; + client->auth_method_valid_neg_role = + AUTH_NEG_ROLE_ORIGINATOR; + } else { + client->send_key_block.transit_bit = 1; + client->auth_method_valid_neg_role = + client->auth_method_neg_role; + } + } else { + client->send_key_block.transit_bit = 0; + client->auth_method_valid_neg_role = AUTH_NEG_ROLE_RESPONDER; + } + + if (client->auth_method_valid_neg_role == AUTH_NEG_ROLE_ORIGINATOR) + acl_set_auth_method_key(client, client->auth_method_valid_count, + client->auth_method_valid_list); + else { + int value = AUTH_OPTION_NOT_PRESENT; + acl_set_auth_method_key(client, 1, &value); + } +} + +static int +acl_set_auth_method_list(struct iscsi_acl *client, unsigned int option_count, + const int *option_list) +{ + int status; + + status = acl_set_option_list(client, option_count, option_list, + &client->auth_method_count, + client->auth_method_list, + AUTH_METHOD_MAX_COUNT, + acl_chk_auth_mthd_optn, + acl_chk_auth_method_list); + + if (status != AUTH_STATUS_NO_ERROR) + return status; + + /* Setting authMethod affects auth_method_valid. */ + acl_set_auth_method_valid(client); + + return AUTH_STATUS_NO_ERROR; +} + +static int +acl_chk_chap_alg_list(unsigned int option_count, const int *option_list) +{ + if (!option_list || option_count < 1) + return 1; + + return 0; +} + +static int +acl_set_chap_alg_list(struct iscsi_acl *client, unsigned int option_count, + const int *option_list) +{ + return acl_set_option_list(client, option_count, option_list, + &client->chap_alg_count, + client->chap_alg_list, + AUTH_CHAP_ALG_MAX_COUNT, + acl_chk_chap_alg_optn, + acl_chk_chap_alg_list); +} + +int +acl_init(int node_type, struct iscsi_session *session) +{ + struct iscsi_acl *client; + struct auth_str_block *rcv_str_blk; + struct auth_str_block *snd_str_blk; + struct auth_large_binary *rcv_chap_chlng; + struct auth_large_binary *snd_chap_chlng; + int value_list[2]; + + if (!session->auth_client_block) + return AUTH_STATUS_ERROR; + client = session->auth_client_block; + + if (!session->auth_recv_string_block) + return AUTH_STATUS_ERROR; + rcv_str_blk = session->auth_recv_string_block; + + if (!session->auth_send_string_block) + return AUTH_STATUS_ERROR; + snd_str_blk = session->auth_send_string_block; + + if (!session->auth_recv_binary_block) + return AUTH_STATUS_ERROR; + rcv_chap_chlng = session->auth_recv_binary_block; + + if (!session->auth_send_binary_block) + return AUTH_STATUS_ERROR; + snd_chap_chlng = session->auth_send_binary_block; + + memset(client, 0, sizeof(*client)); + memset(rcv_str_blk, 0, sizeof(*rcv_str_blk)); + memset(snd_str_blk, 0, sizeof(*snd_str_blk)); + memset(rcv_chap_chlng, 0, sizeof(*rcv_chap_chlng)); + memset(snd_chap_chlng, 0, sizeof(*snd_chap_chlng)); + + client->recv_key_block.str_block = rcv_str_blk->str_block; + client->send_key_block.str_block = snd_str_blk->str_block; + client->recv_chap_challenge.large_binary = rcv_chap_chlng->large_binary; + client->send_chap_challenge.large_binary = snd_chap_chlng->large_binary; + + if (node_type != TYPE_INITIATOR && node_type != TYPE_TARGET) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + client->signature = ACL_SIGNATURE; + client->node_type = (enum auth_node_type) node_type; + client->auth_rmt = 1; + client->passwd_present = 0; + client->chap_challenge_len = AUTH_CHAP_RSP_LEN; + client->ip_sec = 0; + client->session_handle = session; + + client->phase = AUTH_PHASE_CONFIGURE; + client->negotiated_auth_method = AUTH_OPTION_NOT_PRESENT; + client->negotiated_chap_alg = AUTH_OPTION_NOT_PRESENT; + + if (client->node_type == TYPE_INITIATOR) + client->auth_method_neg_role = AUTH_NEG_ROLE_ORIGINATOR; + else + /* Initial value ignored for Target. */ + client->auth_method_neg_role = AUTH_NEG_ROLE_RESPONDER; + + value_list[0] = AUTH_METHOD_CHAP; + value_list[1] = AUTH_OPTION_NONE; + + /* + * Must call after setting auth_rmt, password, + * and auth_method_neg_role + */ + if (acl_set_auth_method_list(client, 2, value_list) != + AUTH_STATUS_NO_ERROR) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + value_list[0] = AUTH_CHAP_ALG_MD5; + + if (acl_set_chap_alg_list(client, 1, value_list) != + AUTH_STATUS_NO_ERROR) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_finish(struct iscsi_acl *client) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + memset(client, 0, sizeof(*client)); + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_set_user_name(struct iscsi_acl *client, const char *username) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE || + acl_chk_string(username, AUTH_STR_MAX_LEN, 0)) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + if (strlcpy(client->username, username, AUTH_STR_MAX_LEN) >= + AUTH_STR_MAX_LEN) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_set_passwd(struct iscsi_acl *client, const unsigned char *passwd_data, + unsigned int passwd_length) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE || + passwd_length > AUTH_STR_MAX_LEN) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + memcpy(client->passwd_data, passwd_data, passwd_length); + client->passwd_length = passwd_length; + client->passwd_present = 1; + + /* Setting password may affect auth_method_valid. */ + acl_set_auth_method_valid(client); + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_set_auth_rmt(struct iscsi_acl *client, int auth_rmt) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + client->auth_rmt = auth_rmt; + + /* Setting auth_rmt may affect auth_method_valid. */ + acl_set_auth_method_valid(client); + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_set_ip_sec(struct iscsi_acl *client, int ip_sec) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_CONFIGURE) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + client->ip_sec = ip_sec; + + return AUTH_STATUS_NO_ERROR; +} + +int +acl_get_dbg_status(struct iscsi_acl *client, int *value) +{ + if (!client || client->signature != ACL_SIGNATURE) + return AUTH_STATUS_ERROR; + + if (client->phase != AUTH_PHASE_DONE) { + client->phase = AUTH_PHASE_ERROR; + return AUTH_STATUS_ERROR; + } + + *value = client->dbg_status; + + return AUTH_STATUS_NO_ERROR; +} + +const char * +acl_dbg_status_to_text(int dbg_status) +{ + /* + * Note: The ordering of this table must match the order + * defined by enum auth_dbg_status in iscsi-auth-client.h. + */ + static char *const dbg_text[AUTH_DBG_STATUS_MAX_COUNT] = { + "Debug status not set", + "Authentication request passed", + "Authentication not enabled", + "Authentication request failed", + "AuthMethod bad", + "CHAP algorithm bad", + "Decrypt password failed", + "Local password too short with no IPSec", + "Unexpected error from authentication server", + "Authentication request status bad", + "Authentication pass status not valid", + "Same key set more than once on send", + "Key value too long on send", + "Too much data on send", + "AuthMethod key expected", + "CHAP algorithm key expected", + "CHAP identifier expected", + "CHAP challenge expected", + "CHAP response expected", + "CHAP username expected", + "AuthMethod key not present", + "AuthMethod negotiation failed", + "AuthMethod negotiated to none", + "CHAP algorithm negotiation failed", + "CHAP challange reflected", + "Local password same as remote", + "Local password not set", + "CHAP identifier bad", + "CHAP challenge bad", + "CHAP response bad", + "Unexpected key present", + "T bit set on response, but not on previous message", + "T bit set on response, but authenticaton not complete", + "Message count limit reached on receive", + "Same key set more than once on receive", + "Key value too long on receive", + "Too much data on receive" + }; + + if (dbg_status < 0 || dbg_status >= AUTH_DBG_STATUS_MAX_COUNT) + return "Unknown error"; + + return dbg_text[dbg_status]; +} + +int +acl_data(unsigned char *out_data, unsigned int *out_length, + unsigned char *in_data, unsigned int in_length) +{ + if (*out_length < in_length) + return 1; /* error */ + + memcpy(out_data, in_data, in_length); + *out_length = in_length; + + return 0; /* no error */ +} + diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-auth-client.h linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-auth-client.h --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-auth-client.h 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-auth-client.h 2005-06-15 17:18:55.780339354 -0500 @@ -0,0 +1,279 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-auth-client.h,v 1.1.2.4 2005/03/15 06:33:39 wysochanski Exp $ + * + * This file is the include file for for iscsi-auth-client.c + */ +#ifndef ISCSIAUTHCLIENT_H +#define ISCSIAUTHCLIENT_H + +struct iscsi_session; + +enum { + AUTH_STR_MAX_LEN = 256, + AUTH_STR_BLOCK_MAX_LEN = 1024, + AUTH_LARGE_BINARY_MAX_LEN = 1024, + AUTH_RECV_END_MAX_COUNT = 10, + ACL_SIGNATURE = 0x5984B2E3, + AUTH_CHAP_RSP_LEN = 16, +}; + +/* + * Note: The ordering of these values are chosen to match + * the ordering of the keys as shown in the iSCSI spec. + * The order of table key_names in acl_get_key_name() + * must match the order defined by enum auth_key_type. + */ +enum auth_key_type { + AUTH_KEY_TYPE_NONE = -1, + AUTH_KEY_TYPE_FIRST = 0, + AUTH_KEY_TYPE_AUTH_METHOD = AUTH_KEY_TYPE_FIRST, + AUTH_KEY_TYPE_CHAP_ALG, + AUTH_KEY_TYPE_CHAP_USERNAME, + AUTH_KEY_TYPE_CHAP_RSP, + AUTH_KEY_TYPE_CHAP_IDENTIFIER, + AUTH_KEY_TYPE_CHAP_CHALLENGE, + AUTH_KEY_TYPE_MAX_COUNT, + AUTH_KEY_TYPE_LAST = AUTH_KEY_TYPE_MAX_COUNT - 1 +}; + +enum { + /* Common options for all keys. */ + AUTH_OPTION_REJECT = -2, + AUTH_OPTION_NOT_PRESENT = -1, + AUTH_OPTION_NONE = 1, + + AUTH_METHOD_CHAP = 2, + AUTH_METHOD_MAX_COUNT = 2, + + AUTH_CHAP_ALG_MD5 = 5, + AUTH_CHAP_ALG_MAX_COUNT = 2 +}; + +enum auth_neg_role { + AUTH_NEG_ROLE_ORIGINATOR = 1, + AUTH_NEG_ROLE_RESPONDER = 2 +}; + +enum auth_status { + AUTH_STATUS_NO_ERROR = 0, + AUTH_STATUS_ERROR, + AUTH_STATUS_PASS, + AUTH_STATUS_FAIL, + AUTH_STATUS_CONTINUE, +}; + +/* + * Note: The order of table dbg_text in acl_dbg_status_to_text() + * must match the ordered defined by enum auth_dbg_status. + */ +enum auth_dbg_status { + AUTH_DBG_STATUS_NOT_SET = 0, + + AUTH_DBG_STATUS_AUTH_PASS, + AUTH_DBG_STATUS_AUTH_RMT_FALSE, + + AUTH_DBG_STATUS_AUTH_FAIL, + + AUTH_DBG_STATUS_AUTH_METHOD_BAD, + AUTH_DBG_STATUS_CHAP_ALG_BAD, + AUTH_DBG_STATUS_PASSWD_DECRYPT_FAILED, + AUTH_DBG_STATUS_PASSWD_TOO_SHORT_WITH_NO_IPSEC, + AUTH_DBG_STATUS_AUTH_SERVER_ERROR, + AUTH_DBG_STATUS_AUTH_STATUS_BAD, + AUTH_DBG_STATUS_AUTHPASS_NOT_VALID, + AUTH_DBG_STATUS_SEND_DUP_SET_KEY_VALUE, + AUTH_DBG_STATUS_SEND_STR_TOO_LONG, + AUTH_DBG_STATUS_SEND_TOO_MUCH_DATA, + + AUTH_DBG_STATUS_AUTH_METHOD_EXPECTED, + AUTH_DBG_STATUS_CHAP_ALG_EXPECTED, + AUTH_DBG_STATUS_CHAP_IDENTIFIER_EXPECTED, + AUTH_DBG_STATUS_CHAP_CHALLENGE_EXPECTED, + AUTH_DBG_STATUS_CHAP_RSP_EXPECTED, + AUTH_DBG_STATUS_CHAP_USERNAME_EXPECTED, + + AUTH_DBG_STATUS_AUTH_METHOD_NOT_PRESENT, + AUTH_DBG_STATUS_AUTH_METHOD_REJECT, + AUTH_DBG_STATUS_AUTH_METHOD_NONE, + AUTH_DBG_STATUS_CHAP_ALG_REJECT, + AUTH_DBG_STATUS_CHAP_CHALLENGE_REFLECTED, + AUTH_DBG_STATUS_PASSWD_IDENTICAL, + + AUTH_DBG_STATUS_LOCAL_PASSWD_NOT_SET, + + AUTH_DBG_STATUS_CHAP_IDENTIFIER_BAD, + AUTH_DBG_STATUS_CHALLENGE_BAD, + AUTH_DBG_STATUS_CHAP_RSP_BAD, + AUTH_DBG_STATUS_UNEXPECTED_KEY_PRESENT, + AUTH_DBG_STATUS_T_BIT_SET_ILLEGAL, + AUTH_DBG_STATUS_T_BIT_SET_PREMATURE, + + AUTH_DBG_STATUS_RECV_MSG_COUNT_LIMIT, + AUTH_DBG_STATUS_RECV_DUP_SET_KEY_VALUE, + AUTH_DBG_STATUS_RECV_STR_TOO_LONG, + AUTH_DBG_STATUS_RECV_TOO_MUCH_DATA, + AUTH_DBG_STATUS_MAX_COUNT +}; + +enum auth_node_type { + TYPE_INITIATOR = 1, + TYPE_TARGET = 2 +}; + +enum auth_phase { + AUTH_PHASE_CONFIGURE = 1, + AUTH_PHASE_NEGOTIATE, + AUTH_PHASE_AUTHENTICATE, + AUTH_PHASE_DONE, + AUTH_PHASE_ERROR +}; + +enum auth_local_state { + AUTH_LOCAL_STATE_SEND_ALG = 1, + AUTH_LOCAL_STATE_RECV_ALG, + AUTH_LOCAL_STATE_RECV_CHALLENGE, + AUTH_LOCAL_STATE_DONE, + AUTH_LOCAL_STATE_ERROR +}; + +enum auth_rmt_state { + AUTH_RMT_STATE_SEND_ALG = 1, + AUTH_RMT_STATE_SEND_CHALLENGE, + AUTH_RMT_STATE_RECV_RSP, + AUTH_RMT_STATE_DONE, + AUTH_RMT_STATE_ERROR +}; + +struct auth_key { + unsigned int present:1; + unsigned int processed:1; + unsigned int value_set:1; + char *string; +}; + +struct auth_large_binary_key { + unsigned int length; + unsigned char *large_binary; +}; + +struct auth_key_block { + unsigned int transit_bit:1; + unsigned int dup_set:1; + unsigned int str_too_long:1; + unsigned int too_much_data:1; + unsigned int blk_length:16; + char *str_block; + struct auth_key key[AUTH_KEY_TYPE_MAX_COUNT]; +}; + +struct auth_str_block { + char str_block[AUTH_STR_BLOCK_MAX_LEN]; +}; + +struct auth_large_binary { + unsigned char large_binary[AUTH_LARGE_BINARY_MAX_LEN]; +}; + +struct iscsi_acl { + unsigned long signature; + + enum auth_node_type node_type; + unsigned int auth_method_count; + int auth_method_list[AUTH_METHOD_MAX_COUNT]; + enum auth_neg_role auth_method_neg_role; + unsigned int chap_alg_count; + int chap_alg_list[AUTH_CHAP_ALG_MAX_COUNT]; + int auth_rmt; + char username[AUTH_STR_MAX_LEN]; + int passwd_present; + unsigned int passwd_length; + unsigned char passwd_data[AUTH_STR_MAX_LEN]; + unsigned int chap_challenge_len; + int ip_sec; + + unsigned int auth_method_valid_count; + int auth_method_valid_list[AUTH_METHOD_MAX_COUNT]; + int auth_method_valid_neg_role; + + int recv_in_progress_flag; + int recv_end_count; + /* + * session for callbacks + */ + struct iscsi_session *session_handle; + enum auth_phase phase; + enum auth_local_state local_state; + enum auth_rmt_state rmt_state; + enum auth_status rmt_auth_status; + enum auth_dbg_status dbg_status; + int negotiated_auth_method; + int negotiated_chap_alg; + int auth_rsp_flag; + int auth_server_error_flag; + int transit_bit_sent_flag; + + unsigned int send_chap_identifier; + struct auth_large_binary_key send_chap_challenge; + char chap_username[AUTH_STR_MAX_LEN]; + + int recv_chap_challenge_status; + struct auth_large_binary_key recv_chap_challenge; + + char scratch_key_value[AUTH_STR_MAX_LEN]; + + struct auth_key_block recv_key_block; + struct auth_key_block send_key_block; +}; + +extern int acl_init(int node_type, struct iscsi_session *session); +extern int acl_finish(struct iscsi_acl *client); + +extern int acl_recv_begin(struct iscsi_acl *client); +extern int acl_recv_end(struct iscsi_acl *client); +extern const char *acl_get_key_name(int key_type); +extern int acl_get_next_key_type(int *key_type); +extern int acl_recv_key_value(struct iscsi_acl *client, int key_type, + const char *user_key_val); +extern int acl_send_key_val(struct iscsi_acl *client, int key_type, + int *key_present, char *user_key_val, + unsigned int max_length); +extern int acl_recv_transit_bit(struct iscsi_acl *client, int value); +extern int acl_send_transit_bit(struct iscsi_acl *client, int *value); +extern int acl_set_user_name(struct iscsi_acl *client, const char *username); +extern int acl_set_passwd(struct iscsi_acl *client, + const unsigned char *pw_data, unsigned int pw_len); +extern int acl_set_auth_rmt(struct iscsi_acl *client, int auth_rmt); +extern int acl_set_ip_sec(struct iscsi_acl *client, int ip_sec); +extern int acl_get_dbg_status(struct iscsi_acl *client, int *value); +extern const char *acl_dbg_status_to_text(int dbg_status); +extern enum auth_dbg_status acl_chap_compute_rsp(struct iscsi_acl *client, + int rmt_auth, + unsigned int id, + unsigned char *challenge_data, + unsigned int challenge_len, + unsigned char *response_data); +extern int acl_chap_auth_request(struct iscsi_acl *client, char *username, + unsigned int id, + unsigned char *challenge_data, + unsigned int challenge_length, + unsigned char *response_data, + unsigned int rsp_length); +extern int acl_data(unsigned char *out_data, unsigned int *out_length, + unsigned char *in_data, unsigned int in_length); +#endif /* #ifndef ISCSIAUTHCLIENT_H */ diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi.h linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi.h --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi.h 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi.h 2005-06-15 17:19:56.688824080 -0500 @@ -0,0 +1,514 @@ +/* + * Constants and structures defined in the iSCSI RFC. + */ +#ifndef ISCSI_H_ +#define ISCSI_H_ + +#include + +#define ISCSI_DRAFT20_VERSION 0x00 + +/* TCP port for iSCSI connections assigned by IANA */ +#define ISCSI_TCP_PORT 3260 + +/* Reserved value for initiator/target task tag */ +#define ISCSI_RSVD_TASK_TAG 0xffffffff + +/* most PDU types have a final bit */ +#define ISCSI_FLAG_FINAL 0x80 + +/* iSCSI Template Header */ +struct iscsi_hdr { + __u8 opcode; + __u8 flags; /* Final bit */ + __u8 rsvd2[2]; + __u8 hlength; /* AHSs total length */ + __u8 dlength[3]; /* Data length */ + __u8 lun[8]; + __u32 itt; + __u8 other[28]; +}; + +/* Opcode encoding bits */ +#define ISCSI_OP_RETRY 0x80 +#define ISCSI_OP_IMMEDIATE 0x40 +#define ISCSI_OPCODE_MASK 0x3F + +/* Client to Server Message Opcode values */ +#define ISCSI_OP_NOOP_OUT 0x00 +#define ISCSI_OP_SCSI_CMD 0x01 +#define ISCSI_OP_TASK_MGT_REQ 0x02 +#define ISCSI_OP_LOGIN_CMD 0x03 +#define ISCSI_OP_TEXT_CMD 0x04 +#define ISCSI_OP_SCSI_DATA 0x05 +#define ISCSI_OP_LOGOUT_CMD 0x06 +#define ISCSI_OP_SNACK_CMD 0x10 + +/* Server to Client Message Opcode values */ +#define ISCSI_OP_NOOP_IN 0x20 +#define ISCSI_OP_SCSI_RSP 0x21 +#define ISCSI_OP_SCSI_TASK_MGT_RSP 0x22 +#define ISCSI_OP_LOGIN_RSP 0x23 +#define ISCSI_OP_TEXT_RSP 0x24 +#define ISCSI_OP_SCSI_DATA_RSP 0x25 +#define ISCSI_OP_LOGOUT_RSP 0x26 +#define ISCSI_OP_R2T 0x31 +#define ISCSI_OP_ASYNC_MSG 0x32 +#define ISCSI_OP_REJECT 0x3f + +/* SCSI Command Header */ +struct iscsi_scsi_cmd_hdr { + __u8 opcode; + __u8 flags; + __u8 rsvd2; + __u8 cmdrn; + __u8 hlength; + __u8 dlength[3]; + __u8 lun[8]; + __u32 itt; + __u32 data_length; + __u32 cmdsn; + __u32 expstatsn; + __u8 scb[16]; /* SCSI Command Block */ + /* Additional Data (Command Dependent) */ +}; + +/* Command PDU flags */ +#define ISCSI_FLAG_CMD_READ 0x40 +#define ISCSI_FLAG_CMD_WRITE 0x20 +#define ISCSI_FLAG_CMD_ATTR_MASK 0x07 /* 3 bits */ + +/* SCSI Command Attribute values */ +#define ISCSI_ATTR_UNTAGGED 0 +#define ISCSI_ATTR_SIMPLE 1 +#define ISCSI_ATTR_ORDERED 2 +#define ISCSI_ATTR_HEAD_OF_QUEUE 3 +#define ISCSI_ATTR_ACA 4 + +/* SCSI Response Header */ +struct iscsi_scsi_rsp_hdr { + __u8 opcode; + __u8 flags; + __u8 response; + __u8 cmd_status; + __u8 hlength; + __u8 dlength[3]; + __u8 rsvd[8]; + __u32 itt; + __u32 rsvd1; + __u32 statsn; + __u32 expcmdsn; + __u32 maxcmdsn; + __u32 expdatasn; + __u32 bi_residual_count; + __u32 residual_count; + /* Response or Sense Data (optional) */ +}; + +/* Command Response PDU flags */ +#define ISCSI_FLAG_CMD_BIDI_OVERFLOW 0x10 +#define ISCSI_FLAG_CMD_BIDI_UNDERFLOW 0x08 +#define ISCSI_FLAG_CMD_OVERFLOW 0x04 +#define ISCSI_FLAG_CMD_UNDERFLOW 0x02 + +/* iSCSI Status values. Valid if Rsp Selector bit is not set */ +#define ISCSI_STATUS_CMD_COMPLETED 0 +#define ISCSI_STATUS_TARGET_FAILURE 1 +#define ISCSI_STATUS_SUBSYS_FAILURE 2 + +/* Asynchronous Message Header */ +struct iscsi_async_msg_hdr { + __u8 opcode; + __u8 flags; + __u8 rsvd2[2]; + __u8 rsvd3; + __u8 dlength[3]; + __u8 lun[8]; + __u8 rsvd4[8]; + __u32 statsn; + __u32 expcmdsn; + __u32 maxcmdsn; + __u8 async_event; + __u8 async_vcode; + __u16 param1; + __u16 param2; + __u16 param3; + __u8 rsvd5[4]; +}; + +/* iSCSI Event Codes */ +#define ISCSI_ASYNC_MSG_SCSI_EVENT 0 +#define ISCSI_ASYNC_MSG_REQUEST_LOGOUT 1 +#define ISCSI_ASYNC_MSG_DROPPING_CONNECTION 2 +#define ISCSI_ASYNC_MSG_DROPPING_ALL_CONNECTIONS 3 +#define ISCSI_ASYNC_MSG_PARAM_NEGOTIATION 4 +#define ISCSI_ASYNC_MSG_VENDOR_SPECIFIC 255 + +/* NOP-Out */ +struct iscsi_nop_out_hdr { + __u8 opcode; + __u8 flags; + __u16 rsvd2; + __u8 rsvd3; + __u8 dlength[3]; + __u8 lun[8]; + __u32 itt; + __u32 ttt; + __u32 cmdsn; + __u32 expstatsn; + __u8 rsvd4[16]; +}; + +/* NOP-In */ +struct iscsi_nop_in_hdr { + __u8 opcode; + __u8 flags; + __u16 rsvd2; + __u8 rsvd3; + __u8 dlength[3]; + __u8 lun[8]; + __u32 itt; + __u32 ttt; + __u32 statsn; + __u32 expcmdsn; + __u32 maxcmdsn; + __u8 rsvd4[12]; +}; + +/* SCSI Task Management Request Header */ +struct iscsi_scsi_task_mgmt_hdr { + __u8 opcode; + __u8 flags; + __u8 rsvd1[2]; + __u8 hlength; + __u8 dlength[3]; + __u8 lun[8]; + __u32 itt; + __u32 rtt; + __u32 cmdsn; + __u32 expstatsn; + __u32 refcmdsn; + __u32 expdatasn; + __u8 rsvd2[8]; +}; + +#define ISCSI_FLAG_TMF_MASK 0x7F + +/* Function values */ +#define ISCSI_TMF_ABORT_TASK 1 +#define ISCSI_TMF_ABORT_TASK_SET 2 +#define ISCSI_TMF_CLEAR_ACA 3 +#define ISCSI_TMF_CLEAR_TASK_SET 4 +#define ISCSI_TMF_LOGICAL_UNIT_RESET 5 +#define ISCSI_TMF_TARGET_WARM_RESET 6 +#define ISCSI_TMF_TARGET_COLD_RESET 7 +#define ISCSI_TMF_TASK_REASSIGN 8 + +/* SCSI Task Management Response Header */ +struct iscsi_scsi_task_mgmt_rsp_hdr { + __u8 opcode; + __u8 flags; + __u8 response; /* see Response values below */ + __u8 qualifier; + __u8 hlength; + __u8 dlength[3]; + __u8 rsvd2[8]; + __u32 itt; + __u32 rtt; + __u32 statsn; + __u32 expcmdsn; + __u32 maxcmdsn; + __u8 rsvd3[12]; +}; + +/* Response values */ +#define ISCSI_TMF_RESP_COMPLETE 0x00 +#define ISCSI_TMF_RESP_UNKNOWN_TASK 0x01 +#define ISCSI_TMF_RESP_UNKNOWN_LUN 0x02 +#define ISCSI_TMF_RESP_TASK_ALLEGIANT 0x03 +#define ISCSI_TMF_RESP_NO_FAILOVER 0x04 +#define ISCSI_TMF_RESP_IN_PRGRESS 0x05 +#define ISCSI_TMF_RESP_REJECTED 0xff + +/* Ready To Transfer Header */ +struct iscsi_r2t_hdr { + __u8 opcode; + __u8 flags; + __u8 rsvd2[2]; + __u8 rsvd3[12]; + __u32 itt; + __u32 ttt; + __u32 statsn; + __u32 expcmdsn; + __u32 maxcmdsn; + __u32 rttsn; + __u32 data_offset; + __u32 data_length; +}; + +/* SCSI Data Hdr */ +struct iscsi_data_hdr { + __u8 opcode; + __u8 flags; + __u8 rsvd2[2]; + __u8 rsvd3; + __u8 dlength[3]; + __u8 lun[8]; + __u32 itt; + __u32 ttt; + __u32 rsvd4; + __u32 expstatsn; + __u32 rsvd5; + __u32 datasn; + __u32 offset; + __u32 rsvd6; + /* Payload */ +}; + +/* SCSI Data Response Hdr */ +struct iscsi_data_rsp_hdr { + __u8 opcode; + __u8 flags; + __u8 rsvd2; + __u8 cmd_status; + __u8 hlength; + __u8 dlength[3]; + __u8 lun[8]; + __u32 itt; + __u32 ttt; + __u32 statsn; + __u32 expcmdsn; + __u32 maxcmdsn; + __u32 datasn; + __u32 offset; + __u32 residual_count; +}; + +/* Data Response PDU flags */ +#define ISCSI_FLAG_DATA_ACK 0x40 +#define ISCSI_FLAG_DATA_OVERFLOW 0x04 +#define ISCSI_FLAG_DATA_UNDERFLOW 0x02 +#define ISCSI_FLAG_DATA_STATUS 0x01 + +/* Text Header */ +struct iscsi_txt_hdr { + __u8 opcode; + __u8 flags; + __u8 rsvd2[2]; + __u8 hlength; + __u8 dlength[3]; + __u8 rsvd4[8]; + __u32 itt; + __u32 ttt; + __u32 cmdsn; + __u32 expstatsn; + __u8 rsvd5[16]; + /* Text - key=value pairs */ +}; + +#define ISCSI_FLAG_TEXT_CONTINUE 0x40 + +/* Text Response Header */ +struct iscsi_txt_rsp_hdr { + __u8 opcode; + __u8 flags; + __u8 rsvd2[2]; + __u8 hlength; + __u8 dlength[3]; + __u8 rsvd4[8]; + __u32 itt; + __u32 ttt; + __u32 statsn; + __u32 expcmdsn; + __u32 maxcmdsn; + __u8 rsvd5[12]; + /* Text Response - key:value pairs */ +}; + +/* Login Header */ +struct iscsi_login_hdr { + __u8 opcode; + __u8 flags; + __u8 max_version; + __u8 min_version; + __u8 hlength; + __u8 dlength[3]; + __u8 isid[6]; + __u16 tsih; + __u32 itt; + __u16 cid; + __u16 rsvd3; + __u32 cmdsn; + __u32 expstatsn; + __u8 rsvd5[16]; +}; + +/* Login PDU flags */ +#define ISCSI_FLAG_LOGIN_TRANSIT 0x80 +#define ISCSI_FLAG_LOGIN_CONTINUE 0x40 +#define ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK 0x0C /* 2 bits */ +#define ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK 0x03 /* 2 bits */ + +#define ISCSI_LOGIN_CURRENT_STAGE(flags) \ + ((flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2) +#define ISCSI_LOGIN_NEXT_STAGE(flags) \ + (flags & ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK) + +/* Login Response Header */ +struct iscsi_login_rsp_hdr { + __u8 opcode; + __u8 flags; + __u8 max_version; + __u8 active_version; + __u8 hlength; + __u8 dlength[3]; + __u8 isid[6]; + __u16 tsih; + __u32 itt; + __u32 rsvd3; + __u32 statsn; + __u32 expcmdsn; + __u32 maxcmdsn; + __u8 status_class; /* see Login RSP ststus classes below */ + __u8 status_detail; /* see Login RSP Status details below */ + __u8 rsvd4[10]; +}; + +/* Login stage (phase) codes for CSG, NSG */ +#define ISCSI_SECURITY_NEGOTIATION_STAGE 0 +#define ISCSI_OP_PARMS_NEGOTIATION_STAGE 1 +#define ISCSI_FULL_FEATURE_PHASE 3 + +/* Login Status response classes */ +#define ISCSI_STATUS_CLS_SUCCESS 0x00 +#define ISCSI_STATUS_CLS_REDIRECT 0x01 +#define ISCSI_STATUS_CLS_INITIATOR_ERR 0x02 +#define ISCSI_STATUS_CLS_TARGET_ERR 0x03 + +/* Login Status response detail codes */ +/* Class-0 (Success) */ +#define ISCSI_LOGIN_STATUS_ACCEPT 0x00 + +/* Class-1 (Redirection) */ +#define ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP 0x01 +#define ISCSI_LOGIN_STATUS_TGT_MOVED_PERM 0x02 + +/* Class-2 (Initiator Error) */ +#define ISCSI_LOGIN_STATUS_INIT_ERR 0x00 +#define ISCSI_LOGIN_STATUS_AUTH_FAILED 0x01 +#define ISCSI_LOGIN_STATUS_TGT_FORBIDDEN 0x02 +#define ISCSI_LOGIN_STATUS_TGT_NOT_FOUND 0x03 +#define ISCSI_LOGIN_STATUS_TGT_REMOVED 0x04 +#define ISCSI_LOGIN_STATUS_NO_VERSION 0x05 +#define ISCSI_LOGIN_STATUS_ISID_ERROR 0x06 +#define ISCSI_LOGIN_STATUS_MISSING_FIELDS 0x07 +#define ISCSI_LOGIN_STATUS_CONN_ADD_FAILED 0x08 +#define ISCSI_LOGIN_STATUS_NO_SESSION_TYPE 0x09 +#define ISCSI_LOGIN_STATUS_NO_SESSION 0x0a +#define ISCSI_LOGIN_STATUS_INVALID_REQUEST 0x0b + +/* Class-3 (Target Error) */ +#define ISCSI_LOGIN_STATUS_TARGET_ERROR 0x00 +#define ISCSI_LOGIN_STATUS_SVC_UNAVAILABLE 0x01 +#define ISCSI_LOGIN_STATUS_NO_RESOURCES 0x02 + +/* Logout Header */ +struct iscsi_logout_hdr { + __u8 opcode; + __u8 flags; + __u8 rsvd1[2]; + __u8 hlength; + __u8 dlength[3]; + __u8 rsvd2[8]; + __u32 itt; + __u16 cid; + __u8 rsvd3[2]; + __u32 cmdsn; + __u32 expstatsn; + __u8 rsvd4[16]; +}; + +/* Logout PDU flags */ +#define ISCSI_FLAG_LOGOUT_REASON_MASK 0x7F + +/* logout reason_code values */ +#define ISCSI_LOGOUT_REASON_CLOSE_SESSION 0 +#define ISCSI_LOGOUT_REASON_CLOSE_CONNECTION 1 +#define ISCSI_LOGOUT_REASON_RECOVERY 2 +#define ISCSI_LOGOUT_REASON_AEN_REQUEST 3 + +/* Logout Response Header */ +struct iscsi_logout_rsp_hdr { + __u8 opcode; + __u8 flags; + __u8 response; /* see Logout response values below */ + __u8 rsvd2; + __u8 hlength; + __u8 dlength[3]; + __u8 rsvd3[8]; + __u32 itt; + __u32 rsvd4; + __u32 statsn; + __u32 expcmdsn; + __u32 maxcmdsn; + __u32 rsvd5; + __u16 t2wait; + __u16 t2retain; + __u32 rsvd6; +}; + +/* logout response status values */ +#define ISCSI_LOGOUT_SUCCESS 0 +#define ISCSI_LOGOUT_CID_NOT_FOUND 1 +#define ISCSI_LOGOUT_RECOVERY_UNSUPPORTED 2 +#define ISCSI_LOGOUT_CLEANUP_FAILED 3 + +/* SNACK Header */ +struct iscsi_snack_hdr { + __u8 opcode; + __u8 flags; + __u8 rsvd2[14]; + __u32 itt; + __u32 begrun; + __u32 runlength; + __u32 expstatsn; + __u32 rsvd3; + __u32 expdatasn; + __u8 rsvd6[8]; +}; + +/* SNACK PDU flags */ +#define ISCSI_FLAG_SNACK_TYPE_MASK 0x0F /* 4 bits */ + +/* Reject Header */ +struct iscsi_reject_hdr { + __u8 opcode; + __u8 flags; + __u8 reason; + __u8 rsvd2; + __u8 rsvd3; + __u8 dlength[3]; + __u8 rsvd4[16]; + __u32 statsn; + __u32 expcmdsn; + __u32 maxcmdsn; + __u32 datasn; + __u8 rsvd5[8]; + /* Text - Rejected hdr */ +}; + +/* Reason for Reject */ +#define ISCSI_REJECT_RESERVED 1 +#define ISCSI_REJECT_DATA_DIGEST_ERROR 2 +#define ISCSI_REJECT_SNACK_REJECT 3 +#define ISCSI_REJECT_ISCSI_PROTOCOL_ERROR 4 +#define ISCSI_REJECT_CMD_NOT_SUPPORTED 5 +#define ISCSI_REJECT_IMM_CMD_REJECT 6 +#define ISCSI_REJECT_TASK_IN_PROGRESS 7 +#define ISCSI_REJECT_INVALID_DATA_ACK 8 +#define ISCSI_REJECT_INVALID_PDU_FIELD 9 +#define ISCSI_REJECT_CANT_GENERATE_TTT 10 +#define ISCSI_REJECT_NEGOTIATION_RESET 11 +#define ISCSI_REJECT_WAITING_FOR_LOGOUT 12 + +#endif diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-initiator.c linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-initiator.c --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-initiator.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-initiator.c 2005-06-15 17:24:27.411879231 -0500 @@ -0,0 +1,538 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-initiator.c,v 1.1.2.47 2005/04/27 06:26:20 mikenc Exp $ + * + * This file contains interfaces required by SCSI mid layer, module + * initialization and shutdown routines. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iscsi-sfnet.h" +#include "iscsi-session.h" +#include "iscsi-protocol.h" +#include "iscsi-task.h" + +/* + * IMPORTANT NOTE: to prevent deadlock, when holding multiple locks, + * the following locking order must be followed at all times: + * + * session->portal_lock - access to a session's portal info + * session->task_lock - access to a session's collections of tasks + * host_lock - mid-layer acquires before calling queuecommand, + * and eh_*. + * + * Note for grabbing task_lock: queuecommand and eh_timed_out are invoked in + * soft_irq context. The former can be invoked in process context as well. + * Every other function where we grab task_lock, we have process context. + * Hence we use spin_lock in replacement_timed_out and spin_lock_bh every + * where else to grab the task lock. + */ + +MODULE_AUTHOR("Mike Christie and Cisco Systems, Inc."); +MODULE_DESCRIPTION("iSCSI initiator"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(ISCSI_DRIVER_VERSION); + +kmem_cache_t *iscsi_task_cache; +static struct scsi_transport_template *iscsi_transportt; + +static unsigned short iscsi_max_sg = 64; +module_param_named(max_sg, iscsi_max_sg, ushort, S_IRUGO); + +static unsigned short iscsi_max_sectors = 256; +module_param_named(max_sectors, iscsi_max_sectors, ushort, S_IRUGO); + +static unsigned int iscsi_can_queue = 512; +module_param_named(can_queue, iscsi_can_queue, uint, S_IRUGO); + +/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */ +#define SNA32_CHECK 2147483648UL + +int +iscsi_sna_lt(u32 n1, u32 n2) +{ + return n1 != n2 && ((n1 < n2 && (n2 - n1 < SNA32_CHECK)) || + (n1 > n2 && (n2 - n1 < SNA32_CHECK))); +} + +/* Serial Number Arithmetic, 32 bits, less than, RFC1982 */ +int +iscsi_sna_lte(u32 n1, u32 n2) +{ + return n1 == n2 || ((n1 < n2 && (n2 - n1 < SNA32_CHECK)) || + (n1 > n2 && (n2 - n1 < SNA32_CHECK))); +} + +/* mark a scsi_cmnd as having a LUN communication failure */ +static inline void +set_lun_comm_failure(struct scsi_cmnd *sc) +{ + sc->sense_buffer[0] = 0x70; + sc->sense_buffer[2] = NOT_READY; + sc->sense_buffer[7] = 0x6; + sc->sense_buffer[12] = 0x08; + sc->sense_buffer[13] = 0x00; +} + +u32 +iscsi_command_attr(struct scsi_cmnd *cmd) +{ + unsigned int attr = ISCSI_ATTR_UNTAGGED; + char msg[2]; + + if (scsi_populate_tag_msg(cmd, msg) == 2) { + switch (msg[0]) { + case MSG_SIMPLE_TAG: + attr = ISCSI_ATTR_SIMPLE; + break; + case MSG_HEAD_TAG: + attr = ISCSI_ATTR_HEAD_OF_QUEUE; + break; + case MSG_ORDERED_TAG: + attr = ISCSI_ATTR_ORDERED; + break; + }; + } + + return attr; +} + +static int +iscsi_slave_configure(struct scsi_device *sdev) +{ + int depth = 1, tag = 0; + + /* + * TODO (one day) - when tcq is not supported we should + * internally queue a command to have one ready to go right + * away when the outstanding one completes. + */ + if (sdev->tagged_supported) { + scsi_activate_tcq(sdev, ISCSI_CMDS_PER_LUN); + depth = ISCSI_CMDS_PER_LUN; + tag = MSG_ORDERED_TAG; + } + + scsi_adjust_queue_depth(sdev, tag, depth); + return 0; +} + +static int +iscsi_eh_abort(struct scsi_cmnd *sc) +{ + struct Scsi_Host *shost = sc->device->host; + struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; + struct iscsi_task *task, *tmf_task; + int ret = FAILED; + + spin_unlock_irq(shost->host_lock); + spin_lock_bh(&session->task_lock); + + /* + * TODO must fix these type of tests + */ + if (!test_bit(SESSION_ESTABLISHED, &session->control_bits)) + goto done; + + task = (struct iscsi_task *)sc->SCp.ptr; + if (!task) { + iscsi_host_err(session, "eh_abort cmnd already done\n"); + ret = SUCCESS; + goto done; + } + + if (task->itt == ISCSI_RSVD_TASK_TAG) { + __iscsi_complete_task(task); + ret = SUCCESS; + goto done; + } + + /* + * TODO need a iscsi_dev_info + */ + iscsi_host_info(session, "Sending ABORT TASK for task itt %u\n", + task->itt); + + tmf_task = session->mgmt_task; + memset(tmf_task, 0, sizeof(*tmf_task)); + iscsi_init_task(tmf_task); + tmf_task->session = session; + tmf_task->lun = task->lun; + /* + * this will become the refcmdsn + */ + tmf_task->cmdsn = task->cmdsn; + tmf_task->rtt = task->itt; + set_bit(ISCSI_TASK_ABORT, &tmf_task->flags); + + if (!iscsi_exec_task_mgmt(tmf_task, session->abort_timeout)) { + ret = SUCCESS; + goto done; + } + /* + * TMF may have failed if the task completed first (check here) + */ + if (!sc->SCp.ptr) + ret = SUCCESS; + done: + spin_unlock_bh(&session->task_lock); + spin_lock_irq(shost->host_lock); + + return ret; +} + +static int +iscsi_eh_device_reset(struct scsi_cmnd *sc) +{ + struct Scsi_Host *shost = sc->device->host; + struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; + struct iscsi_task *task; + int ret = FAILED; + + spin_unlock_irq(shost->host_lock); + spin_lock_bh(&session->task_lock); + + if (!test_bit(SESSION_ESTABLISHED, &session->control_bits)) + goto done; + + task = session->mgmt_task; + memset(task, 0, sizeof(*task)); + iscsi_init_task(task); + task->session = session; + task->lun = sc->device->lun; + __set_bit(ISCSI_TASK_ABORT_TASK_SET, &task->flags); + + /* + * need a iscsi_dev_info + */ + iscsi_host_info(session, "Sending ABORT TASK SET\n"); + if (!iscsi_exec_task_mgmt(task, session->abort_timeout)) { + ret = SUCCESS; + goto done; + } + + iscsi_init_task(task); + __set_bit(ISCSI_TASK_LU_RESET, &task->flags); + + iscsi_host_info(session, "Sending LU RESET\n"); + if (!iscsi_exec_task_mgmt(task, session->reset_timeout)) + ret = SUCCESS; + done: + spin_unlock_bh(&session->task_lock); + spin_lock_irq(shost->host_lock); + + return ret; +} + +static int +iscsi_eh_host_reset(struct scsi_cmnd *sc) +{ + struct Scsi_Host *shost = sc->device->host; + struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; + struct iscsi_task *task; + int ret = FAILED; + + spin_unlock_irq(shost->host_lock); + spin_lock_bh(&session->task_lock); + + if (!test_bit(SESSION_ESTABLISHED, &session->control_bits)) + goto done; + + task = session->mgmt_task; + memset(task, 0, sizeof(*task)); + iscsi_init_task(task); + task->session = session; + __set_bit(ISCSI_TASK_TGT_WARM_RESET, &task->flags); + + iscsi_host_info(session, "Sending TARGET WARM RESET\n"); + if (iscsi_exec_task_mgmt(task, session->reset_timeout)) + /* + * no other options + */ + iscsi_drop_session(session); + + done: + /* + * if we failed, scsi-ml will put us offline + * and if we were successful it will redrive the + * commands, so we clean everything up from our side + * so scsi-ml can retake ownership of the commands. + * (At this point the tx and rx threads will not be + * touching the commands since either the session + * was dropped or we just did a target reset) + */ + iscsi_flush_queues(session, ISCSI_MAX_LUNS, DID_BUS_BUSY); + + spin_unlock_bh(&session->task_lock); + if (iscsi_wait_for_session(session, 0)) + ret = SUCCESS; + spin_lock_irq(shost->host_lock); + + return ret; +} + +void +iscsi_complete_command(struct scsi_cmnd *sc) +{ + sc->SCp.ptr = NULL; + sc->scsi_done(sc); +} + +/** + * iscsi_queuecommand - queuecommand interface for the iSCSI driver. + * @sc: scsi command from the midlayer + * @done: Call back function to be called once the command is executed. + **/ +static int +iscsi_queuecommand(struct scsi_cmnd *sc, void (*done) (struct scsi_cmnd *)) +{ + struct Scsi_Host *host = sc->device->host; + struct iscsi_session *session = (struct iscsi_session *)host->hostdata; + struct iscsi_task *task; + int ret = 0; + + spin_unlock_irq(host->host_lock); + + spin_lock_bh(&session->task_lock); + if (test_bit(SESSION_REPLACEMENT_TIMEDOUT, &session->control_bits)) { + spin_unlock_bh(&session->task_lock); + if (printk_ratelimit()) + iscsi_host_warn(session, "lun%u: Session terminating, " + "failing to queue cdb 0x%x and any " + "following commands\n", sc->device->lun, sc->cmnd[0]); + goto fail; + } + + /* make sure we can complete it properly later */ + sc->scsi_done = done; + sc->result = 0; + memset(&sc->SCp, 0, sizeof(sc->SCp)); + + /* + * alloc a task and add it to the pending queue so + * the tx-thread will run it + */ + task = iscsi_alloc_task(session); + if (!task) { + ret = SCSI_MLQUEUE_HOST_BUSY; + goto done; + } + + task->lun = sc->device->lun; + task->scsi_cmnd = sc; + sc->SCp.ptr = (char *)task; + list_add_tail(&task->queue, &session->pending_queue); + + iscsi_wake_tx_thread(TX_SCSI_COMMAND, session); + done: + spin_unlock_bh(&session->task_lock); + spin_lock_irq(host->host_lock); + return ret; + + fail: + spin_lock_irq(host->host_lock); + sc->result = DID_NO_CONNECT << 16; + sc->resid = sc->request_bufflen; + set_lun_comm_failure(sc); + + done(sc); + return 0; +} + +int +iscsi_destroy_host(struct Scsi_Host *shost) +{ + struct iscsi_session *session = (struct iscsi_session *)shost->hostdata; + + if (!test_bit(SESSION_CREATED, &session->control_bits)) + return -EINVAL; + + if (test_and_set_bit(SESSION_RELEASING, &session->control_bits)) + return -EINVAL; + + scsi_remove_host(shost); + iscsi_destroy_session(session); + scsi_host_put(shost); + return 0; +} + +static struct scsi_host_template iscsi_driver_template = { + .name = "SFNet iSCSI driver", + .proc_name = ISCSI_PROC_NAME, + .module = THIS_MODULE, + .queuecommand = iscsi_queuecommand, + .eh_abort_handler = iscsi_eh_abort, + .eh_device_reset_handler = iscsi_eh_device_reset, + .eh_host_reset_handler = iscsi_eh_host_reset, + .skip_settle_delay = 1, + .slave_configure = iscsi_slave_configure, + .this_id = -1, + .cmd_per_lun = ISCSI_CMDS_PER_LUN, + .use_clustering = ENABLE_CLUSTERING, + .emulated = 1, + .shost_attrs = iscsi_host_attrs, + .sdev_attrs = iscsi_dev_attrs, +}; + +int +iscsi_create_host(struct iscsi_session_ioctl *ioctld) +{ + struct Scsi_Host *shost; + struct iscsi_session *session; + int rc; + + shost = scsi_host_alloc(&iscsi_driver_template, sizeof(*session)); + if (!shost) + return -ENOMEM; + + shost->max_id = ISCSI_MAX_TARGETS; + shost->max_lun = ISCSI_MAX_LUNS; + shost->max_channel = ISCSI_MAX_CHANNELS; + shost->max_cmd_len = ISCSI_MAX_CMD_LEN; + shost->transportt = iscsi_transportt; + + shost->max_sectors = iscsi_max_sectors; + if (!shost->max_sectors || shost->max_sectors > ISCSI_MAX_SECTORS) { + iscsi_err("Invalid max_sectors of %d using %d\n", + shost->max_sectors, ISCSI_MAX_SECTORS); + shost->max_sectors = ISCSI_MAX_SECTORS; + } + + shost->sg_tablesize = iscsi_max_sg; + if (!shost->sg_tablesize || shost->sg_tablesize > ISCSI_MAX_SG) { + iscsi_err("Invalid max_sq of %d using %d\n", + shost->sg_tablesize, ISCSI_MAX_SG); + shost->sg_tablesize = ISCSI_MAX_SG; + } + + shost->can_queue = iscsi_can_queue; + if (!shost->can_queue || shost->can_queue > ISCSI_MAX_CAN_QUEUE) { + iscsi_err("Invalid can_queue of %d using %d\n", + shost->can_queue, ISCSI_MAX_CAN_QUEUE); + shost->can_queue = ISCSI_MAX_CAN_QUEUE; + } + + session = (struct iscsi_session *)shost->hostdata; + memset(session, 0, sizeof(*session)); + session->shost = shost; + + rc = iscsi_create_session(session, ioctld); + if (rc) { + scsi_host_put(shost); + return rc; + } + + rc = scsi_add_host(shost, NULL); + if (rc) { + iscsi_destroy_session(session); + scsi_host_put(shost); + return rc; + } + + scsi_scan_host(shost); + set_bit(SESSION_CREATED, &session->control_bits); + + return 0; +} + +/* + * This function must only be called when the sysfs and + * ioctl interfaces are inaccessible. For example when + * the module_exit function is executed the driver's sysfs + * and ioctl entry points will return "no device". + */ +static void +iscsi_destroy_all_hosts(void) +{ + struct iscsi_session *session, *tmp; + + list_for_each_entry_safe(session, tmp, &iscsi_sessions, list) + iscsi_destroy_host(session->shost); +} + +static int +iscsi_reboot_notifier_function(struct notifier_block *this, + unsigned long code, void *unused) +{ + iscsi_destroy_all_hosts(); + iscsi_notice("Driver shutdown completed\n"); + return NOTIFY_DONE; +} + +/* XXX move this to driver model shutdown */ +static struct notifier_block iscsi_reboot_notifier = { + .notifier_call = iscsi_reboot_notifier_function, + .next = NULL, + .priority = 255, /* priority, might need to have a + * relook at the value + */ +}; + +static int +__init iscsi_init(void) +{ + iscsi_notice("Loading iscsi_sfnet version %s\n", ISCSI_DRIVER_VERSION); + + /* pool of iscsi tasks */ + iscsi_task_cache = kmem_cache_create("iscsi_task_cache", + sizeof(struct iscsi_task), 0, + SLAB_NO_REAP, NULL, NULL); + + if (!iscsi_task_cache) { + iscsi_err("kmem_cache_create failed\n"); + return -ENOMEM; + } + + iscsi_transportt = iscsi_attach_transport(&iscsi_fnt); + if (!iscsi_transportt) + goto free_cache; + + if (iscsi_register_interface()) + goto release_transport; + + register_reboot_notifier(&iscsi_reboot_notifier); + return 0; + + release_transport: + iscsi_release_transport(iscsi_transportt); + free_cache: + kmem_cache_destroy(iscsi_task_cache); + iscsi_err("Failed to init driver\n"); + return -ENODEV; +} + +static void +__exit iscsi_cleanup(void) +{ + unregister_reboot_notifier(&iscsi_reboot_notifier); + iscsi_unregister_interface(); + iscsi_destroy_all_hosts(); + iscsi_release_transport(iscsi_transportt); + kmem_cache_destroy(iscsi_task_cache); +} +module_init(iscsi_init); +module_exit(iscsi_cleanup); diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-ioctl.c linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-ioctl.c --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-ioctl.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-ioctl.c 2005-06-15 17:18:33.387472100 -0500 @@ -0,0 +1,146 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-ioctl.c,v 1.1.2.20 2005/04/12 19:18:33 mikenc Exp $ + * + * This file handles iscsi ioctl calls + */ +#include +#include +#include +#include + +#include "iscsi-session.h" +#include "iscsi-ioctl.h" +#include "iscsi-sfnet.h" + +static int +iscsi_ioctl_establish_session(void __user *arg) +{ + int rc; + struct iscsi_session *session; + struct iscsi_session_ioctl *ioctld; + + ioctld = kmalloc(sizeof(*ioctld), GFP_KERNEL); + if (!ioctld) { + iscsi_err("Couldn't allocate space for session ioctl data\n"); + return -ENOMEM; + } + + if (copy_from_user(ioctld, (void *)arg, sizeof(*ioctld))) { + iscsi_err("Cannot copy session ioctl data\n"); + kfree(ioctld); + return -EFAULT; + } + + if (ioctld->ioctl_version != ISCSI_SESSION_IOCTL_VERSION) { + iscsi_err("ioctl version %u incorrect, expecting %u\n", + ioctld->ioctl_version, ISCSI_SESSION_IOCTL_VERSION); + return -EINVAL; + } + + /* + * TODO - should update wait for the relogin? + */ + session = iscsi_find_session(ioctld->target_name, ioctld->isid, + ioctld->portal.tag); + if (session) { + rc = iscsi_update_session(session, ioctld); + scsi_host_put(session->shost); + } else if (ioctld->update) { + iscsi_err("Could not find session to update\n"); + rc = -EAGAIN; + } else + rc = iscsi_create_host(ioctld); + + kfree(ioctld); + return rc; +} + +static int +iscsi_ctl_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *_arg = (void __user *) arg; + + if (!capable(CAP_SYS_ADMIN)) + return -EACCES; + + if (_IOC_TYPE(cmd) != ISCSI_IOCTL) + return -ENOTTY; + + if (cmd == ISCSI_ESTABLISH_SESSION) + return iscsi_ioctl_establish_session(_arg); + + iscsi_err("Requested ioctl not found\n"); + return -EINVAL; +} + +static struct class_simple *iscsictl_sysfs_class; +static int control_major; +static const char *control_name = "iscsictl"; + +static struct file_operations control_fops = { + .owner = THIS_MODULE, + .ioctl = iscsi_ctl_ioctl, +}; + +int +iscsi_register_interface(void) +{ + control_major = register_chrdev(0, control_name, &control_fops); + if (control_major < 0) { + iscsi_err("Failed to register the control device\n"); + return -ENODEV; + } + iscsi_notice("Control device major number %d\n", control_major); + + /* Provide udev support for the control device. */ + iscsictl_sysfs_class = class_simple_create(THIS_MODULE, + "iscsi_control"); + if (!iscsictl_sysfs_class) + goto unreg_chrdev; + + if (!class_simple_device_add(iscsictl_sysfs_class, + MKDEV(control_major, 0), NULL, + "iscsictl")) + goto destroy_iscsictl_cls; + + if (register_ioctl32_conversion(ISCSI_ESTABLISH_SESSION, NULL)) + goto remove_iscsictl_cls; + + return 0; + + remove_iscsictl_cls: + class_simple_device_remove(MKDEV(control_major, 0)); + destroy_iscsictl_cls: + class_simple_destroy(iscsictl_sysfs_class); + unreg_chrdev: + unregister_chrdev(control_major, control_name); + return -ENODEV; +} + +void +iscsi_unregister_interface(void) +{ + unregister_ioctl32_conversion(ISCSI_ESTABLISH_SESSION); + class_simple_device_remove(MKDEV(control_major, 0)); + class_simple_destroy(iscsictl_sysfs_class); + unregister_chrdev(control_major, control_name); +} diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-ioctl.h linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-ioctl.h --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-ioctl.h 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-ioctl.h 2005-06-15 17:19:56.688824080 -0500 @@ -0,0 +1,76 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-ioctl.h,v 1.1.2.19 2005/04/26 17:44:50 mikenc Exp $ + * + * include for ioctl calls between the daemon and the kernel module + */ +#ifndef ISCSI_IOCTL_H_ +#define ISCSI_IOCTL_H_ + +#include +#include + +#include "iscsi-protocol.h" +#include "iscsi-portal.h" +#include "iscsi-auth-client.h" + +/* + * still not sure if the ioctl is going to stay + * so can fix up later + */ +struct iscsi_session_ioctl { + __u32 ioctl_version; + __u32 config_number; + int update; + __u8 isid[6]; + /* + * passwords can contain NULL chars so we need + * the length. + */ + int password_length; + char username[AUTH_STR_MAX_LEN]; + unsigned char password[AUTH_STR_MAX_LEN]; + int password_length_in; + char username_in[AUTH_STR_MAX_LEN]; + unsigned char password_in[AUTH_STR_MAX_LEN]; + unsigned char target_name[TARGET_NAME_MAXLEN + 1]; + unsigned char initiator_name[TARGET_NAME_MAXLEN + 1]; + unsigned char initiator_alias[TARGET_NAME_MAXLEN + 1]; + int login_timeout; + int active_timeout; + int idle_timeout; + int ping_timeout; + int abort_timeout; + int reset_timeout; + int replacement_timeout; + struct iscsi_portal_info portal; +}; + +#define ISCSI_SESSION_IOCTL_VERSION 25 + +/* + * ioctls + */ +#define ISCSI_EST_SESS_CMD 0 + +#define ISCSI_IOCTL 0xbc +#define ISCSI_ESTABLISH_SESSION _IOW(ISCSI_IOCTL, ISCSI_EST_SESS_CMD, \ + struct iscsi_session_ioctl) +#endif diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-login.c linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-login.c --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-login.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-login.c 2005-06-15 17:19:04.390135160 -0500 @@ -0,0 +1,1377 @@ +/* + * iSCSI login library + * Copyright (C) 2001 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-login.c,v 1.1.2.14 2005/06/09 06:23:21 smithan Exp $ + * + * + * Formation of iSCSI login pdu, processing the login response and other + * functions are defined here + */ +#include "iscsi-session.h" +#include "iscsi-login.h" +#include "iscsi-protocol.h" +#include "iscsi-sfnet.h" + +/* caller is assumed to be well-behaved and passing NUL terminated strings */ +int +iscsi_add_text(struct iscsi_session *session, struct iscsi_hdr *pdu, char *data, + int max_data_length, char *param, char *value) +{ + int param_len = strlen(param); + int value_len = strlen(value); + int length = param_len + 1 + value_len + 1; /* param, separator, + * value, and trailing + * NULL + */ + int pdu_length = ntoh24(pdu->dlength); + char *text = data; + char *end = data + max_data_length; + char *pdu_text; + + /* find the end of the current text */ + text += pdu_length; + pdu_text = text; + pdu_length += length; + + if (text + length >= end) { + iscsi_host_notice(session, "Failed to add login text " + "'%s=%s'\n", param, value); + return 0; + } + + /* param */ + strncpy(text, param, param_len); + text += param_len; + + /* separator */ + *text++ = ISCSI_TEXT_SEPARATOR; + + /* value */ + strncpy(text, value, value_len); + text += value_len; + + /* NUL */ + *text++ = '\0'; + + /* update the length in the PDU header */ + hton24(pdu->dlength, pdu_length); + + return 1; +} + +static int +iscsi_find_key_value(char *param, char *pdu, char *pdu_end, char **value_start, + char **value_end) +{ + char *str = param; + char *text = pdu; + char *value; + + if (value_start) + *value_start = NULL; + if (value_end) + *value_end = NULL; + + /* make sure they contain the same bytes */ + while (*str) { + if (text >= pdu_end) + return 0; + if (*text == '\0') + return 0; + if (*str != *text) + return 0; + str++; + text++; + } + + if ((text >= pdu_end) || (*text == '\0') + || (*text != ISCSI_TEXT_SEPARATOR)) { + return 0; + } + + /* find the value */ + value = text + 1; + + /* find the end of the value */ + while ((text < pdu_end) && (*text)) + text++; + + if (value_start) + *value_start = value; + if (value_end) + *value_end = text; + + return 1; +} + +static enum iscsi_login_status +get_auth_key_type(struct iscsi_acl *auth_client, char **data, char *end) +{ + char *key; + char *value = NULL; + char *value_end = NULL; + char *text = *data; + + int keytype = AUTH_KEY_TYPE_NONE; + + while (acl_get_next_key_type(&keytype) == AUTH_STATUS_NO_ERROR) { + key = (char *)acl_get_key_name(keytype); + + if (key && iscsi_find_key_value(key, text, end, &value, + &value_end)) { + if (acl_recv_key_value(auth_client, keytype, value) != + AUTH_STATUS_NO_ERROR) { + iscsi_err("login negotiation failed, can't " + "accept %s in security stage\n", + text); + return LOGIN_NEGOTIATION_FAILED; + } + text = value_end; + *data = text; + return LOGIN_OK; + } + } + iscsi_err("Login negotiation failed, can't accept %s in security " + "stage\n", text); + return LOGIN_NEGOTIATION_FAILED; +} + +static enum iscsi_login_status +get_security_text_keys(struct iscsi_session *session, char **data, + struct iscsi_acl *auth_client, char *end) +{ + char *text = *data; + char *value = NULL; + char *value_end = NULL; + size_t size; + int tag; + enum iscsi_login_status ret; + + /* + * a few keys are possible in Security stage + * which the auth code doesn't care about, but + * which we might want to see, or at least not + * choke on. + */ + if (iscsi_find_key_value("TargetAlias", text, end, &value, + &value_end)) { + size = value_end - value; + session->target_alias = kmalloc(size + 1, GFP_ATOMIC); + if (!session->target_alias) { + /* Alias not critical. So just print an error */ + iscsi_host_err(session, "Login failed to allocate " + "alias\n"); + *data = value_end; + return LOGIN_OK; + } + memcpy(session->target_alias, value, size); + session->target_alias[size] = '\0'; + text = value_end; + } else if (iscsi_find_key_value("TargetAddress", text, end, &value, + &value_end)) { + /* + * if possible, change the session's + * ip_address and port to the new + * TargetAddress + */ + if (iscsi_update_address(session, value)) { + text = value_end; + } else { + iscsi_host_err(session, "Login redirection failed, " + "can't handle redirection to %s\n", + value); + return LOGIN_REDIRECTION_FAILED; + } + } else if (iscsi_find_key_value("TargetPortalGroupTag", text, end, + &value, &value_end)) { + /* + * We should have already obtained this + * via discovery. + * We've already picked an isid, so the + * most we can do is confirm we reached + * the portal group we were expecting to + */ + tag = simple_strtoul(value, NULL, 0); + if (session->portal_group_tag >= 0) { + if (tag != session->portal_group_tag) { + iscsi_host_err(session, "Portal group tag " + "mismatch, expected %u, " + "received %u\n", + session->portal_group_tag, tag); + return LOGIN_WRONG_PORTAL_GROUP; + } + } else + /* we now know the tag */ + session->portal_group_tag = tag; + + text = value_end; + } else { + /* + * any key we don't recognize either + * goes to the auth code, or we choke + * on it + */ + ret = get_auth_key_type(auth_client, &text, end); + if (ret != LOGIN_OK) + return ret; + } + *data = text; + return LOGIN_OK; +} + +static enum iscsi_login_status +get_op_params_text_keys(struct iscsi_session *session, char **data, char *end) +{ + char *text = *data; + char *value = NULL; + char *value_end = NULL; + size_t size; + + if (iscsi_find_key_value("TargetAlias", text, end, &value, + &value_end)) { + size = value_end - value; + if (session->target_alias && + strlen(session->target_alias) == size && + memcmp(session->target_alias, value, size) == 0) { + *data = value_end; + return LOGIN_OK; + } + kfree(session->target_alias); + session->target_alias = kmalloc(size + 1, GFP_ATOMIC); + if (!session->target_alias) { + /* Alias not critical. So just print an error */ + iscsi_host_err(session, "Login failed to allocate " + "alias\n"); + *data = value_end; + return LOGIN_OK; + } + memcpy(session->target_alias, value, size); + session->target_alias[size] = '\0'; + text = value_end; + } else if (iscsi_find_key_value("TargetAddress", text, end, &value, + &value_end)) { + if (iscsi_update_address(session, value)) + text = value_end; + else { + iscsi_host_err(session, "Login redirection failed, " + "can't handle redirection to %s\n", + value); + return LOGIN_REDIRECTION_FAILED; + } + } else if (iscsi_find_key_value("TargetPortalGroupTag", text, end, + &value, &value_end)) { + /* + * confirm we reached the portal group we were expecting to + */ + int tag = simple_strtoul(value, NULL, 0); + if (session->portal_group_tag >= 0) { + if (tag != session->portal_group_tag) { + iscsi_host_err(session, "Portal group tag " + "mismatch, expected %u, " + "received %u\n", + session->portal_group_tag, tag); + return LOGIN_WRONG_PORTAL_GROUP; + } + } else + /* we now know the tag */ + session->portal_group_tag = tag; + + text = value_end; + } else if (iscsi_find_key_value("InitialR2T", text, end, &value, + &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) { + if (value && !strcmp(value, "Yes")) + session->initial_r2t = 1; + } else + session->irrelevant_keys_bitmap |= + IRRELEVANT_INITIALR2T; + text = value_end; + } else if (iscsi_find_key_value("ImmediateData", text, end, &value, + &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) { + if (value && (strcmp(value, "Yes") == 0)) + session->immediate_data = 1; + else + session->immediate_data = 0; + } else + session->irrelevant_keys_bitmap |= + IRRELEVANT_IMMEDIATEDATA; + text = value_end; + } else if (iscsi_find_key_value("MaxRecvDataSegmentLength", text, end, + &value, &value_end)) { + session->max_xmit_data_segment_len = + simple_strtoul(value, NULL, 0); + text = value_end; + } else if (iscsi_find_key_value("FirstBurstLength", text, end, &value, + &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) + session->first_burst_len = + simple_strtoul(value, NULL, 0); + else + session->irrelevant_keys_bitmap |= + IRRELEVANT_FIRSTBURSTLENGTH; + text = value_end; + } else if (iscsi_find_key_value("MaxBurstLength", text, end, &value, + &value_end)) { + /* + * we don't really care, since it's a limit on the target's + * R2Ts, but record it anwyay + */ + if (session->type == ISCSI_SESSION_TYPE_NORMAL) + session->max_burst_len = simple_strtoul(value, NULL, 0); + else + session->irrelevant_keys_bitmap |= + IRRELEVANT_MAXBURSTLENGTH; + text = value_end; + } else if (iscsi_find_key_value("HeaderDigest", text, end, &value, + &value_end)) { + if (strcmp(value, "None") == 0) { + if (session->header_digest != ISCSI_DIGEST_CRC32C) + session->header_digest = ISCSI_DIGEST_NONE; + else { + iscsi_host_err(session, "Login negotiation " + "failed, HeaderDigest=CRC32C " + "is required, can't accept " + "%s\n", text); + return LOGIN_NEGOTIATION_FAILED; + } + } else if (strcmp(value, "CRC32C") == 0) { + if (session->header_digest != ISCSI_DIGEST_NONE) + session->header_digest = ISCSI_DIGEST_CRC32C; + else { + iscsi_host_err(session, "Login negotiation " + "failed, HeaderDigest=None is " + "required, can't accept %s\n", + text); + return LOGIN_NEGOTIATION_FAILED; + } + } else { + iscsi_host_err(session, "Login negotiation failed, " + "can't accept %s\n", text); + return LOGIN_NEGOTIATION_FAILED; + } + text = value_end; + } else if (iscsi_find_key_value("DataDigest", text, end, &value, + &value_end)) { + if (strcmp(value, "None") == 0) { + if (session->data_digest != ISCSI_DIGEST_CRC32C) + session->data_digest = ISCSI_DIGEST_NONE; + else { + iscsi_host_err(session, "Login negotiation " + "failed, DataDigest=CRC32C " + "is required, can't accept " + "%s\n", text); + return LOGIN_NEGOTIATION_FAILED; + } + } else if (strcmp(value, "CRC32C") == 0) { + if (session->data_digest != ISCSI_DIGEST_NONE) + session->data_digest = ISCSI_DIGEST_CRC32C; + else { + iscsi_host_err(session, "Login negotiation " + "failed, DataDigest=None is " + "required, can't accept %s\n", + text); + return LOGIN_NEGOTIATION_FAILED; + } + } else { + iscsi_host_err(session, "Login negotiation failed, " + "can't accept %s\n", text); + return LOGIN_NEGOTIATION_FAILED; + } + text = value_end; + } else if (iscsi_find_key_value("DefaultTime2Wait", text, end, &value, + &value_end)) { + session->def_time2wait = simple_strtoul(value, NULL, 0); + text = value_end; + } else if (iscsi_find_key_value("DefaultTime2Retain", text, end, + &value, &value_end)) { + session->def_time2retain = simple_strtoul(value, NULL, 0); + text = value_end; + } else if (iscsi_find_key_value("OFMarker", text, end, &value, + &value_end)) + /* result function is AND, target must honor our No */ + text = value_end; + else if (iscsi_find_key_value("OFMarkInt", text, end, &value, + &value_end)) + /* we don't do markers, so we don't care */ + text = value_end; + else if (iscsi_find_key_value("IFMarker", text, end, &value, + &value_end)) + /* result function is AND, target must honor our No */ + text = value_end; + else if (iscsi_find_key_value("IFMarkInt", text, end, &value, + &value_end)) + /* we don't do markers, so we don't care */ + text = value_end; + else if (iscsi_find_key_value("DataPDUInOrder", text, end, &value, + &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) { + if (value && !strcmp(value, "Yes")) + session->data_pdu_in_order = 1; + } else + session->irrelevant_keys_bitmap |= + IRRELEVANT_DATAPDUINORDER; + text = value_end; + } else if (iscsi_find_key_value ("DataSequenceInOrder", text, end, + &value, &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) { + if (value && !strcmp(value, "Yes")) + session->data_seq_in_order = 1; + } else + session->irrelevant_keys_bitmap |= + IRRELEVANT_DATASEQUENCEINORDER; + text = value_end; + } else if (iscsi_find_key_value("MaxOutstandingR2T", text, end, &value, + &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) { + if (strcmp(value, "1")) { + iscsi_host_err(session, "Login negotiation " + "failed, can't accept Max" + "OutstandingR2T %s\n", value); + return LOGIN_NEGOTIATION_FAILED; + } + } else + session->irrelevant_keys_bitmap |= + IRRELEVANT_MAXOUTSTANDINGR2T; + text = value_end; + } else if (iscsi_find_key_value("MaxConnections", text, end, &value, + &value_end)) { + if (session->type == ISCSI_SESSION_TYPE_NORMAL) { + if (strcmp(value, "1")) { + iscsi_host_err(session, "Login negotiation " + "failed, can't accept Max" + "Connections %s\n", value); + return LOGIN_NEGOTIATION_FAILED; + } + } else + session->irrelevant_keys_bitmap |= + IRRELEVANT_MAXCONNECTIONS; + text = value_end; + } else if (iscsi_find_key_value("ErrorRecoveryLevel", text, end, + &value, &value_end)) { + if (strcmp(value, "0")) { + iscsi_host_err(session, "Login negotiation failed, " + "can't accept ErrorRecovery %s\n", + value); + return LOGIN_NEGOTIATION_FAILED; + } + text = value_end; + } else if (iscsi_find_key_value ("X-com.cisco.protocol", text, end, + &value, &value_end)) { + if (strcmp(value, "NotUnderstood") && + strcmp(value, "Reject") && + strcmp(value, "Irrelevant") && + strcmp(value, "draft20")) { + /* if we didn't get a compatible protocol, fail */ + iscsi_host_err(session, "Login version mismatch, " + "can't accept protocol %s\n", value); + return LOGIN_VERSION_MISMATCH; + } + text = value_end; + } else if (iscsi_find_key_value("X-com.cisco.PingTimeout", text, end, + &value, &value_end)) + /* we don't really care what the target ends up using */ + text = value_end; + else if (iscsi_find_key_value("X-com.cisco.sendAsyncText", text, end, + &value, &value_end)) + /* we don't bother for the target response */ + text = value_end; + else { + iscsi_host_err(session, "Login negotiation failed, couldn't " + "recognize text %s\n", text); + return LOGIN_NEGOTIATION_FAILED; + } + *data = text; + return LOGIN_OK; +} + +static enum iscsi_login_status +check_security_stage_status(struct iscsi_session *session, + struct iscsi_acl *auth_client) +{ + int debug_status = 0; + + switch (acl_recv_end(auth_client)) { + case AUTH_STATUS_CONTINUE: + /* continue sending PDUs */ + break; + + case AUTH_STATUS_PASS: + break; + + case AUTH_STATUS_NO_ERROR: /* treat this as an error, + * since we should get a + * different code + */ + case AUTH_STATUS_ERROR: + case AUTH_STATUS_FAIL: + default: + if (acl_get_dbg_status(auth_client, &debug_status) != + AUTH_STATUS_NO_ERROR) + iscsi_host_err(session, "Login authentication failed " + "with target %s, %s\n", + session->target_name, + acl_dbg_status_to_text(debug_status)); + else + iscsi_host_err(session, "Login authentication failed " + "with target %s\n", + session->target_name); + return LOGIN_AUTHENTICATION_FAILED; + } + return LOGIN_OK; +} + +/* + * this assumes the text data is always NULL terminated. The caller can + * always arrange for that by using a slightly larger buffer than the max PDU + * size, and then appending a NULL to the PDU. + */ +static enum iscsi_login_status +iscsi_process_login_response(struct iscsi_session *session, + struct iscsi_login_rsp_hdr *login_rsp_pdu, + char *data, int max_data_length) +{ + int transit = login_rsp_pdu->flags & ISCSI_FLAG_LOGIN_TRANSIT; + char *text = data; + char *end; + int pdu_current_stage, pdu_next_stage; + enum iscsi_login_status ret; + struct iscsi_acl *auth_client = NULL; + + if (session->password_length) + auth_client = session->auth_client_block ? + session->auth_client_block : NULL; + + end = text + ntoh24(login_rsp_pdu->dlength) + 1; + if (end >= (data + max_data_length)) { + iscsi_host_err(session, "Login failed, process_login_response " + "buffer too small to guarantee NULL " + "termination\n"); + return LOGIN_FAILED; + } + + /* guarantee a trailing NUL */ + *end = '\0'; + + /* if the response status was success, sanity check the response */ + if (login_rsp_pdu->status_class == ISCSI_STATUS_CLS_SUCCESS) { + /* check the active version */ + if (login_rsp_pdu->active_version != ISCSI_DRAFT20_VERSION) { + iscsi_host_err(session, "Login version mismatch, " + "received incompatible active iSCSI " + "version 0x%02x, expected version " + "0x%02x\n", + login_rsp_pdu->active_version, + ISCSI_DRAFT20_VERSION); + return LOGIN_VERSION_MISMATCH; + } + + /* make sure the current stage matches */ + pdu_current_stage = (login_rsp_pdu->flags & + ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2; + if (pdu_current_stage != session->current_stage) { + iscsi_host_err(session, "Received invalid login PDU, " + "current stage mismatch, session %d, " + "response %d\n", session->current_stage, + pdu_current_stage); + return LOGIN_INVALID_PDU; + } + + /* + * make sure that we're actually advancing if the T-bit is set + */ + pdu_next_stage = login_rsp_pdu->flags & + ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK; + if (transit && (pdu_next_stage <= session->current_stage)) + return LOGIN_INVALID_PDU; + } + + if (session->current_stage == ISCSI_SECURITY_NEGOTIATION_STAGE) { + if (acl_recv_begin(auth_client) != AUTH_STATUS_NO_ERROR) { + iscsi_host_err(session, "Login failed because " + "acl_recv_begin failed\n"); + return LOGIN_FAILED; + } + + if (acl_recv_transit_bit(auth_client, transit) != + AUTH_STATUS_NO_ERROR) { + iscsi_host_err(session, "Login failed because " + "acl_recv_transit_bit failed\n"); + return LOGIN_FAILED; + } + } + + /* scan the text data */ + while (text && (text < end)) { + /* skip any NULs separating each text key=value pair */ + while ((text < end) && (*text == '\0')) + text++; + if (text >= end) + break; + + /* handle keys appropriate for each stage */ + switch (session->current_stage) { + case ISCSI_SECURITY_NEGOTIATION_STAGE:{ + ret = get_security_text_keys(session, &text, + auth_client, end); + if (ret != LOGIN_OK) + return ret; + break; + } + case ISCSI_OP_PARMS_NEGOTIATION_STAGE:{ + ret = get_op_params_text_keys(session, &text, + end); + if (ret != LOGIN_OK) + return ret; + break; + } + default: + return LOGIN_FAILED; + } + } + + if (session->current_stage == ISCSI_SECURITY_NEGOTIATION_STAGE) { + ret = check_security_stage_status(session, auth_client); + if (ret != LOGIN_OK) + return ret; + } + /* record some of the PDU fields for later use */ + session->tsih = ntohs(login_rsp_pdu->tsih); + session->exp_cmd_sn = ntohl(login_rsp_pdu->expcmdsn); + session->max_cmd_sn = ntohl(login_rsp_pdu->maxcmdsn); + if (login_rsp_pdu->status_class == ISCSI_STATUS_CLS_SUCCESS) + session->exp_stat_sn = ntohl(login_rsp_pdu->statsn) + 1; + + if (transit) { + /* advance to the next stage */ + session->partial_response = 0; + session->current_stage = login_rsp_pdu->flags & + ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK; + session->irrelevant_keys_bitmap = 0; + } else + /* + * we got a partial response, don't advance, + * more negotiation to do + */ + session->partial_response = 1; + + return LOGIN_OK; /* this PDU is ok, though the login process + * may not be done yet + */ +} + +static int +add_params_normal_session(struct iscsi_session *session, struct iscsi_hdr *pdu, + char *data, int max_data_length) +{ + char value[AUTH_STR_MAX_LEN]; + + /* these are only relevant for normal sessions */ + if (!iscsi_add_text(session, pdu, data, max_data_length, "InitialR2T", + session->initial_r2t ? "Yes" : "No")) + return 0; + + if (!iscsi_add_text(session, pdu, data, max_data_length, + "ImmediateData", + session->immediate_data ? "Yes" : "No")) + return 0; + + sprintf(value, "%d", session->max_burst_len); + if (!iscsi_add_text(session, pdu, data, max_data_length, + "MaxBurstLength", value)) + return 0; + + sprintf(value, "%d",session->first_burst_len); + if (!iscsi_add_text(session, pdu, data, max_data_length, + "FirstBurstLength", value)) + return 0; + + /* these we must have */ + if (!iscsi_add_text(session, pdu, data, max_data_length, + "MaxOutstandingR2T", "1")) + return 0; + if (!iscsi_add_text(session, pdu, data, max_data_length, + "MaxConnections", "1")) + return 0; + if (!iscsi_add_text(session, pdu, data, max_data_length, + "DataPDUInOrder", "Yes")) + return 0; + if (!iscsi_add_text(session, pdu, data, max_data_length, + "DataSequenceInOrder", "Yes")) + return 0; + + return 1; +} + +static int +add_vendor_specific_text(struct iscsi_session *session, struct iscsi_hdr *pdu, + char *data, int max_data_length) +{ + char value[AUTH_STR_MAX_LEN]; + + /* + * adjust the target's PingTimeout for normal sessions, + * so that it matches the driver's ping timeout. The + * network probably has the same latency in both + * directions, so the values ought to match. + */ + if (session->ping_timeout >= 0) { + sprintf(value, "%d", session->ping_timeout); + if (!iscsi_add_text(session, pdu, data, max_data_length, + "X-com.cisco.PingTimeout", value)) + return 0; + } + + if (session->send_async_text >= 0) + if (!iscsi_add_text(session, pdu, data, max_data_length, + "X-com.cisco.sendAsyncText", + session->send_async_text ? "Yes" : "No")) + return 0; + + /* + * vendor-specific protocol specification. list of protocol level + * strings in order of preference allowable values are: draft + * (e.g. draft8), rfc (e.g. rfc666). + * For example: "X-com.cisco.protocol=draft20,draft8" requests draft 20, + * or 8 if 20 isn't supported. "X-com.cisco.protocol=draft8,draft20" + * requests draft 8, or 20 if 8 isn't supported. Targets that + * understand this key SHOULD return the protocol level they selected + * as a response to this key, though the active_version may be + * sufficient to distinguish which protocol was chosen. + * Note: This probably won't work unless we start in op param stage, + * since the security stage limits what keys we can send, and we'd need + * to have sent this on the first PDU of the login. Keep sending it for + * informational use, and so that we can sanity check things later if + * the RFC and draft20 are using the same active version number, + * but have non-trivial differences. + */ + if (!iscsi_add_text(session, pdu, data, max_data_length, + "X-com.cisco.protocol", "draft20")) + return 0; + + return 1; +} + +static int +check_irrelevant_keys(struct iscsi_session *session, struct iscsi_hdr *pdu, + char *data, int max_data_length) +{ + /* If you receive irrelevant keys, just check them from the irrelevant + * keys bitmap and respond with the key=Irrelevant text + */ + + if (session->irrelevant_keys_bitmap & IRRELEVANT_MAXCONNECTIONS) + if (!iscsi_add_text(session, pdu, data, max_data_length, + "MaxConnections", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_INITIALR2T) + if (!iscsi_add_text(session, pdu, data, max_data_length, + "InitialR2T", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_IMMEDIATEDATA) + if (!iscsi_add_text(session, pdu, data, max_data_length, + "ImmediateData", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_MAXBURSTLENGTH) + if (!iscsi_add_text(session, pdu, data, max_data_length, + "MaxBurstLength", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_FIRSTBURSTLENGTH) + if (!iscsi_add_text(session, pdu, data, max_data_length, + "FirstBurstLength", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_MAXOUTSTANDINGR2T) + if (!iscsi_add_text(session, pdu, data, max_data_length, + "MaxOutstandingR2T", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_DATAPDUINORDER) + if (!iscsi_add_text(session, pdu, data, max_data_length, + "DataPDUInOrder", "Irrelevant")) + return 0; + + if (session->irrelevant_keys_bitmap & IRRELEVANT_DATASEQUENCEINORDER ) + if (!iscsi_add_text(session, pdu, data, max_data_length, + "DataSequenceInOrder", "Irrelevant")) + return 0; + + return 1; +} + +static int +fill_crc_digest_text(struct iscsi_session *session, struct iscsi_hdr *pdu, + char *data, int max_data_length) +{ + switch (session->header_digest) { + case ISCSI_DIGEST_NONE: + if (!iscsi_add_text(session, pdu, data, max_data_length, + "HeaderDigest", "None")) + return 0; + break; + case ISCSI_DIGEST_CRC32C: + if (!iscsi_add_text(session, pdu, data, max_data_length, + "HeaderDigest", "CRC32C")) + return 0; + break; + case ISCSI_DIGEST_CRC32C_NONE: + if (!iscsi_add_text(session, pdu, data, max_data_length, + "HeaderDigest", "CRC32C,None")) + return 0; + break; + default: + case ISCSI_DIGEST_NONE_CRC32C: + if (!iscsi_add_text(session, pdu, data, max_data_length, + "HeaderDigest", "None,CRC32C")) + return 0; + break; + } + + switch (session->data_digest) { + case ISCSI_DIGEST_NONE: + if (!iscsi_add_text(session, pdu, data, max_data_length, + "DataDigest", "None")) + return 0; + break; + case ISCSI_DIGEST_CRC32C: + if (!iscsi_add_text(session, pdu, data, max_data_length, + "DataDigest", "CRC32C")) + return 0; + break; + case ISCSI_DIGEST_CRC32C_NONE: + if (!iscsi_add_text(session, pdu, data, max_data_length, + "DataDigest", "CRC32C,None")) + return 0; + break; + default: + case ISCSI_DIGEST_NONE_CRC32C: + if (!iscsi_add_text(session, pdu, data, max_data_length, + "DataDigest", "None,CRC32C")) + return 0; + break; + } + return 1; +} + +static int +fill_op_params_text(struct iscsi_session *session, struct iscsi_hdr *pdu, + char *data, int max_data_length, int *transit) +{ + char value[AUTH_STR_MAX_LEN]; + + /* we always try to go from op params to full feature stage */ + session->current_stage = ISCSI_OP_PARMS_NEGOTIATION_STAGE; + session->next_stage = ISCSI_FULL_FEATURE_PHASE; + *transit = 1; + + /* + * If we haven't gotten a partial response, then either we shouldn't be + * here, or we just switched to this stage, and need to start offering + * keys. + */ + if (!session->partial_response) { + /* + * request the desired settings the first time + * we are in this stage + */ + if (!fill_crc_digest_text(session, pdu, data, max_data_length)) + return 0; + + sprintf(value, "%d", session->max_recv_data_segment_len); + if (!iscsi_add_text(session, pdu, data, max_data_length, + "MaxRecvDataSegmentLength", value)) + return 0; + + sprintf(value, "%d", session->def_time2wait); + if (!iscsi_add_text(session, pdu, data, max_data_length, + "DefaultTime2Wait", value)) + return 0; + + sprintf(value, "%d", session->def_time2retain); + if (!iscsi_add_text(session, pdu, data, max_data_length, + "DefaultTime2Retain", value)) + return 0; + + if (!iscsi_add_text(session, pdu, data, max_data_length, + "IFMarker", "No")) + return 0; + + if (!iscsi_add_text(session, pdu, data, max_data_length, + "OFMarker", "No")) + return 0; + + if (!iscsi_add_text(session, pdu, data, max_data_length, + "ErrorRecoveryLevel", "0")) + return 0; + + if (session->type == ISCSI_SESSION_TYPE_NORMAL) + if (!add_params_normal_session(session, pdu, data, + max_data_length)) + return 0; + + /* + * Note: 12.22 forbids vendor-specific keys on discovery + * sessions, so the caller is violating the spec if it asks for + * these on a discovery session. + */ + if (session->vendor_specific_keys) + if (!add_vendor_specific_text(session, pdu, data, + max_data_length)) + return 0; + } else if (!check_irrelevant_keys(session, pdu, data, max_data_length)) + return 0; + + return 1; +} + +static void +enum_auth_keys(struct iscsi_acl *auth_client, struct iscsi_hdr *pdu, + char *data, int max_data_length, int keytype) +{ + int present = 0, rc; + char *key = (char *)acl_get_key_name(keytype); + int key_length = key ? strlen(key) : 0; + int pdu_length = ntoh24(pdu->dlength); + char *auth_value = data + pdu_length + key_length + 1; + unsigned int max_length = max_data_length - (pdu_length + + key_length + 1); + + /* + * add the key/value pairs the auth code wants to send + * directly to the PDU, since they could in theory be large. + */ + rc = acl_send_key_val(auth_client, keytype, &present, auth_value, + max_length); + if ((rc == AUTH_STATUS_NO_ERROR) && present) { + /* actually fill in the key */ + strncpy(&data[pdu_length], key, key_length); + pdu_length += key_length; + data[pdu_length] = '='; + pdu_length++; + /* + * adjust the PDU's data segment length + * to include the value and trailing NUL + */ + pdu_length += strlen(auth_value) + 1; + hton24(pdu->dlength, pdu_length); + } +} + +static int +fill_security_params_text(struct iscsi_session *session, struct iscsi_hdr *pdu, + struct iscsi_acl *auth_client, char *data, + int max_data_length, int *transit) +{ + int keytype = AUTH_KEY_TYPE_NONE; + int rc = acl_send_transit_bit(auth_client, transit); + + /* see if we're ready for a stage change */ + if (rc != AUTH_STATUS_NO_ERROR) + return 0; + + if (*transit) { + /* + * discovery sessions can go right to full-feature phase, + * unless they want to non-standard values for the few relevant + * keys, or want to offer vendor-specific keys + */ + if (session->type == ISCSI_SESSION_TYPE_DISCOVERY) + if ((session->header_digest != ISCSI_DIGEST_NONE) || + (session->data_digest != ISCSI_DIGEST_NONE) || + (session-> max_recv_data_segment_len != + DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH) || + session->vendor_specific_keys) + session->next_stage = + ISCSI_OP_PARMS_NEGOTIATION_STAGE; + else + session->next_stage = ISCSI_FULL_FEATURE_PHASE; + else + session->next_stage = ISCSI_OP_PARMS_NEGOTIATION_STAGE; + } else + session->next_stage = ISCSI_SECURITY_NEGOTIATION_STAGE; + + /* enumerate all the keys the auth code might want to send */ + while (acl_get_next_key_type(&keytype) == AUTH_STATUS_NO_ERROR) + enum_auth_keys(auth_client, pdu, data, max_data_length, + keytype); + + return 1; +} + +/** + * iscsi_make_login_pdu - Prepare the login pdu to be sent to iSCSI target. + * @session: session for which login is initiated. + * @pdu: login header + * @data: contains text keys to be negotiated during login + * @max_data_length: data size + * + * Description: + * Based on whether authentication is enabled or not, corresponding text + * keys are filled up in login pdu. + * + **/ +static int +iscsi_make_login_pdu(struct iscsi_session *session, struct iscsi_hdr *pdu, + char *data, int max_data_length) +{ + int transit = 0; + int ret; + struct iscsi_login_hdr *login_pdu = (struct iscsi_login_hdr *)pdu; + struct iscsi_acl *auth_client = NULL; + + if (session->password_length) + auth_client = session->auth_client_block ? + session->auth_client_block : NULL; + + /* initialize the PDU header */ + memset(login_pdu, 0, sizeof(*login_pdu)); + login_pdu->opcode = ISCSI_OP_LOGIN_CMD | ISCSI_OP_IMMEDIATE; + login_pdu->cid = 0; + memcpy(login_pdu->isid, session->isid, sizeof(session->isid)); + login_pdu->tsih = 0; + login_pdu->cmdsn = htonl(session->cmd_sn); + /* don't increment on immediate */ + login_pdu->min_version = ISCSI_DRAFT20_VERSION; + login_pdu->max_version = ISCSI_DRAFT20_VERSION; + + /* we have to send 0 until full-feature stage */ + login_pdu->expstatsn = htonl(session->exp_stat_sn); + + /* + * the very first Login PDU has some additional requirements, + * and we need to decide what stage to start in. + */ + if (session->current_stage == ISCSI_INITIAL_LOGIN_STAGE) { + if (session->initiator_name && session->initiator_name[0]) { + if (!iscsi_add_text(session, pdu, data, max_data_length, + "InitiatorName", session->initiator_name)) + return 0; + } else { + iscsi_host_err(session, "InitiatorName is required " + "on the first Login PDU\n"); + return 0; + } + if (session->initiator_alias && session->initiator_alias[0]) { + if (!iscsi_add_text(session, pdu, data, max_data_length, + "InitiatorAlias", session->initiator_alias)) + return 0; + } + + if ((session->target_name && session->target_name[0]) && + (session->type == ISCSI_SESSION_TYPE_NORMAL)) { + if (!iscsi_add_text(session, pdu, data, max_data_length, + "TargetName", session->target_name)) + return 0; + } + + if (!iscsi_add_text(session, pdu, data, max_data_length, + "SessionType", (session->type == + ISCSI_SESSION_TYPE_DISCOVERY) ? "Discovery" : "Normal")) + return 0; + + if (auth_client) + /* we're prepared to do authentication */ + session->current_stage = session->next_stage = + ISCSI_SECURITY_NEGOTIATION_STAGE; + else + /* can't do any authentication, skip that stage */ + session->current_stage = session->next_stage = + ISCSI_OP_PARMS_NEGOTIATION_STAGE; + } + + /* fill in text based on the stage */ + switch (session->current_stage) { + case ISCSI_OP_PARMS_NEGOTIATION_STAGE:{ + ret = fill_op_params_text(session, pdu, data, + max_data_length, &transit); + if (!ret) + return ret; + break; + } + case ISCSI_SECURITY_NEGOTIATION_STAGE:{ + ret = fill_security_params_text(session, pdu, + auth_client, data, + max_data_length, + &transit); + if (!ret) + return ret; + break; + } + case ISCSI_FULL_FEATURE_PHASE: + iscsi_host_err(session, "Can't send login PDUs in full " + "feature phase\n"); + return 0; + default: + iscsi_host_err(session, "Can't send login PDUs in unknown " + "stage %d\n", session->current_stage); + return 0; + } + + /* fill in the flags */ + login_pdu->flags = 0; + login_pdu->flags |= session->current_stage << 2; + if (transit) { + /* transit to the next stage */ + login_pdu->flags |= session->next_stage; + login_pdu->flags |= ISCSI_FLAG_LOGIN_TRANSIT; + } else + /* next == current */ + login_pdu->flags |= session->current_stage; + + return 1; +} + +static enum iscsi_login_status +check_for_authentication(struct iscsi_session *session, + struct iscsi_acl **auth_client) +{ + /* prepare for authentication */ + if (acl_init(TYPE_INITIATOR, session) != AUTH_STATUS_NO_ERROR) { + iscsi_host_err(session, "Couldn't initialize authentication\n"); + return LOGIN_FAILED; + } + + *auth_client = session->auth_client_block; + + if (session->username && + (acl_set_user_name(*auth_client, session->username) != + AUTH_STATUS_NO_ERROR)) { + iscsi_host_err(session, "Couldn't set username\n"); + goto end; + } + + if (session->password && (acl_set_passwd(*auth_client, + session->password, session->password_length) != + AUTH_STATUS_NO_ERROR)) { + iscsi_host_err(session, "Couldn't set password\n"); + goto end; + } + + if (acl_set_ip_sec(*auth_client, 1) != AUTH_STATUS_NO_ERROR) { + iscsi_host_err(session, "Couldn't set IPSec\n"); + goto end; + } + + if (acl_set_auth_rmt(*auth_client, session->bidirectional_auth) != + AUTH_STATUS_NO_ERROR) { + iscsi_host_err(session, "Couldn't set remote authentication\n"); + goto end; + } + return LOGIN_OK; + + end: + if (*auth_client && acl_finish(*auth_client) != AUTH_STATUS_NO_ERROR) + iscsi_host_err(session, "Login failed, error finishing " + "auth_client\n"); + *auth_client = NULL; + return LOGIN_FAILED; +} + +static enum iscsi_login_status +check_status_login_response(struct iscsi_session *session, + struct iscsi_login_rsp_hdr *login_rsp_pdu, + char *data, int max_data_length, int *final) +{ + enum iscsi_login_status ret; + + switch (login_rsp_pdu->status_class) { + case ISCSI_STATUS_CLS_SUCCESS: + /* process this response and possibly continue sending PDUs */ + ret = iscsi_process_login_response(session, login_rsp_pdu, + data, max_data_length); + if (ret != LOGIN_OK) /* pass back whatever + * error we discovered + */ + *final = 1; + break; + case ISCSI_STATUS_CLS_REDIRECT: + /* + * we need to process this response to get the + * TargetAddress of the redirect, but we don't care + * about the return code. + */ + iscsi_process_login_response(session, login_rsp_pdu, + data, max_data_length); + ret = LOGIN_OK; + *final = 1; + case ISCSI_STATUS_CLS_INITIATOR_ERR: + if (login_rsp_pdu->status_detail == + ISCSI_LOGIN_STATUS_AUTH_FAILED) { + iscsi_host_err(session, "Login failed to authenticate " + "with target %s\n", + session->target_name); + } + ret = LOGIN_OK; + *final = 1; + default: + /* + * some sort of error, login terminated unsuccessfully, + * though this function did it's job. + * the caller must check the status_class and + * status_detail and decide what to do next. + */ + ret = LOGIN_OK; + *final = 1; + } + return ret; +} + +/** + * iscsi_login - attempt to login to the target. + * @session: login is initiated over this session + * @buffer: holds login pdu + * @bufsize: size of login pdu + * @status_class: holds either success or failure as status of login + * @status_detail: contains details based on the login status + * + * Description: + * The caller must check the status class to determine if the login + * succeeded. A return of 1 does not mean the login succeeded, it just + * means this function worked, and the status class is valid info. + * This allows the caller to decide whether or not to retry logins, so + * that we don't have any policy logic here. + **/ +enum iscsi_login_status +iscsi_login(struct iscsi_session *session, char *buffer, size_t bufsize, + uint8_t *status_class, uint8_t *status_detail) +{ + struct iscsi_acl *auth_client = NULL; + struct iscsi_hdr pdu; + struct iscsi_login_rsp_hdr *login_rsp_pdu; + char *data; + int received_pdu = 0; + int max_data_length; + int final = 0; + enum iscsi_login_status ret = LOGIN_FAILED; + + /* prepare the session */ + session->cmd_sn = 1; + session->exp_cmd_sn = 1; + session->max_cmd_sn = 1; + session->exp_stat_sn = 0; + + session->current_stage = ISCSI_INITIAL_LOGIN_STAGE; + session->partial_response = 0; + + if (session->password_length) { + ret = check_for_authentication(session, &auth_client); + if (ret != LOGIN_OK) + return ret; + } + + /* + * exchange PDUs until the login stage is complete, or an error occurs + */ + do { + final = 0; + login_rsp_pdu = (struct iscsi_login_rsp_hdr *)&pdu; + ret = LOGIN_FAILED; + + memset(buffer, 0, bufsize); + data = buffer; + max_data_length = bufsize; + + /* + * fill in the PDU header and text data based on the login + * stage that we're in + */ + if (!iscsi_make_login_pdu(session, &pdu, data, + max_data_length)) { + iscsi_host_err(session, "login failed, couldn't make " + "a login PDU\n"); + ret = LOGIN_FAILED; + goto done; + } + + /* send a PDU to the target */ + if (!iscsi_send_pdu(session, &pdu, ISCSI_DIGEST_NONE, + data, ISCSI_DIGEST_NONE)) { + /* + * FIXME: caller might want us to distinguish I/O + * error and timeout. Might want to switch portals on + * timeouts, but + * not I/O errors. + */ + iscsi_host_err(session, "Login I/O error, failed to " + "send a PDU\n"); + ret = LOGIN_IO_ERROR; + goto done; + } + + /* read the target's response into the same buffer */ + if (!iscsi_recv_pdu(session, &pdu, ISCSI_DIGEST_NONE, data, + max_data_length, ISCSI_DIGEST_NONE)) { + /* + * FIXME: caller might want us to distinguish I/O + * error and timeout. Might want to switch portals on + * timeouts, but not I/O errors. + */ + iscsi_host_err(session, "Login I/O error, failed to " + "receive a PDU\n"); + ret = LOGIN_IO_ERROR; + goto done; + } + + received_pdu = 1; + + /* check the PDU response type */ + if (pdu.opcode == (ISCSI_OP_LOGIN_RSP | 0xC0)) { + /* + * it's probably a draft 8 login response, + * which we can't deal with + */ + iscsi_host_err(session, "Received iSCSI draft 8 login " + "response opcode 0x%x, expected draft " + "20 login response 0x%2x\n", + pdu.opcode, ISCSI_OP_LOGIN_RSP); + ret = LOGIN_VERSION_MISMATCH; + goto done; + } else if (pdu.opcode != ISCSI_OP_LOGIN_RSP) { + ret = LOGIN_INVALID_PDU; + goto done; + } + + /* + * give the caller the status class and detail from the last + * login response PDU received + */ + if (status_class) + *status_class = login_rsp_pdu->status_class; + if (status_detail) + *status_detail = login_rsp_pdu->status_detail; + ret = check_status_login_response(session, login_rsp_pdu, data, + max_data_length, &final); + if (final) + goto done; + } while (session->current_stage != ISCSI_FULL_FEATURE_PHASE); + + ret = LOGIN_OK; + + done: + if (auth_client && acl_finish(auth_client) != AUTH_STATUS_NO_ERROR) { + iscsi_host_err(session, "Login failed, error finishing " + "auth_client\n"); + if (ret == LOGIN_OK) + ret = LOGIN_FAILED; + } + + return ret; +} diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-login.h linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-login.h --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-login.h 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-login.h 2005-06-15 17:19:07.117753701 -0500 @@ -0,0 +1,86 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-login.h,v 1.1.2.7 2005/03/15 06:33:39 wysochanski Exp $ + * + * include for iSCSI login + */ +#ifndef ISCSI_LOGIN_H_ +#define ISCSI_LOGIN_H_ + +struct iscsi_session; +struct iscsi_hdr; + +#define ISCSI_SESSION_TYPE_NORMAL 0 +#define ISCSI_SESSION_TYPE_DISCOVERY 1 + +/* not defined by iSCSI, but used in the login code to determine + * when to send the initial Login PDU + */ +#define ISCSI_INITIAL_LOGIN_STAGE -1 + +#define ISCSI_TEXT_SEPARATOR '=' + +enum iscsi_login_status { + LOGIN_OK = 0, /* library worked, but caller must check + * the status class and detail + */ + LOGIN_IO_ERROR, /* PDU I/O failed, connection have been + * closed or reset + */ + LOGIN_FAILED, /* misc. failure */ + LOGIN_VERSION_MISMATCH, /* incompatible iSCSI protocol version */ + LOGIN_NEGOTIATION_FAILED, /* didn't like a key value + * (or received an unknown key) + */ + LOGIN_AUTHENTICATION_FAILED, /* auth code indicated failure */ + LOGIN_WRONG_PORTAL_GROUP, /* portal group tag didn't match + * the one required + */ + LOGIN_REDIRECTION_FAILED, /* couldn't handle the redirection + * requested by the target + */ + LOGIN_INVALID_PDU, /* received an incorrect opcode, + * or bogus fields in a PDU + */ +}; + +/* implemented in iscsi-login.c for use on all platforms */ +extern int iscsi_add_text(struct iscsi_session *session, struct iscsi_hdr *pdu, + char *data, int max_data_length, char *param, + char *value); +extern enum iscsi_login_status iscsi_login(struct iscsi_session *session, + char *buffer, size_t bufsize, + uint8_t * status_class, + uint8_t * status_detail); + +/* Digest types */ +#define ISCSI_DIGEST_NONE 0 +#define ISCSI_DIGEST_CRC32C 1 +#define ISCSI_DIGEST_CRC32C_NONE 2 /* offer both, prefer CRC32C */ +#define ISCSI_DIGEST_NONE_CRC32C 3 /* offer both, prefer None */ + +#define IRRELEVANT_MAXCONNECTIONS 0x01 +#define IRRELEVANT_INITIALR2T 0x02 +#define IRRELEVANT_IMMEDIATEDATA 0x04 +#define IRRELEVANT_MAXBURSTLENGTH 0x08 +#define IRRELEVANT_FIRSTBURSTLENGTH 0x10 +#define IRRELEVANT_MAXOUTSTANDINGR2T 0x20 +#define IRRELEVANT_DATAPDUINORDER 0x40 +#define IRRELEVANT_DATASEQUENCEINORDER 0x80 + +#endif diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-network.c linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-network.c --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-network.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-network.c 2005-06-15 17:18:33.387472100 -0500 @@ -0,0 +1,257 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-network.c,v 1.1.2.8 2005/03/29 19:35:07 mikenc Exp $ + * + * Contains functions to handle socket operations + */ +#include +#include + +#include "iscsi-session.h" +#include "iscsi-sfnet.h" + +/* + * decode common network errno values into more useful strings. + * strerror would be nice right about now. + */ +static char * +iscsi_strerror(int errno) +{ + switch (errno) { + case EIO: + return "I/O error"; + case EINTR: + return "Interrupted system call"; + case ENXIO: + return "No such device or address"; + case EFAULT: + return "Bad address"; + case EBUSY: + return "Device or resource busy"; + case EINVAL: + return "Invalid argument"; + case EPIPE: + return "Broken pipe"; + case ENONET: + return "Machine is not on the network"; + case ECOMM: + return "Communication error on send"; + case EPROTO: + return "Protocol error"; + case ENOTUNIQ: + return "Name not unique on network"; + case ENOTSOCK: + return "Socket operation on non-socket"; + case ENETDOWN: + return "Network is down"; + case ENETUNREACH: + return "Network is unreachable"; + case ENETRESET: + return "Network dropped connection because of reset"; + case ECONNABORTED: + return "Software caused connection abort"; + case ECONNRESET: + return "Connection reset by peer"; + case ESHUTDOWN: + return "Cannot send after shutdown"; + case ETIMEDOUT: + return "Connection timed out"; + case ECONNREFUSED: + return "Connection refused"; + case EHOSTDOWN: + return "Host is down"; + case EHOSTUNREACH: + return "No route to host"; + default: + return ""; + } +} + +/* create and connect a new socket for this session */ +int +iscsi_connect(struct iscsi_session *session) +{ + struct socket *socket; + int arg = 1; + int rc; + + if (session->socket) + return 0; + + rc = sock_create_kern(PF_INET, SOCK_STREAM, IPPROTO_TCP, &socket); + if (rc < 0) { + iscsi_host_err(session, "Failed to create socket, rc %d\n", rc); + return rc; + } + + session->socket = socket; + socket->sk->sk_allocation = GFP_ATOMIC; + + /* no delay in sending */ + rc = socket->ops->setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, + (char *)&arg, sizeof(arg)); + if (rc) { + iscsi_host_err(session, "Failed to setsockopt TCP_NODELAY, rc " + "%d\n", rc); + goto done; + } + + if (session->tcp_window_size) { + /* + * Should we be accessing the sk_recv/send_buf directly like + * NFS (sock_setsockopt will be bounded by the sysctl limits)? + */ + sock_setsockopt(socket, SOL_SOCKET, SO_RCVBUF, + (char *)&session->tcp_window_size, + sizeof(session->tcp_window_size)); + sock_setsockopt(socket, SOL_SOCKET, SO_SNDBUF, + (char *)&session->tcp_window_size, + sizeof(session->tcp_window_size)); + } + + rc = socket->ops->connect(socket, &session->addr, + sizeof(struct sockaddr), 0); + done: + if (rc) { + if (signal_pending(current)) + iscsi_host_err(session, "Connect failed due to " + "driver timeout\n"); + else + iscsi_host_err(session, "Connect failed with rc %d: " + "%s\n", rc, iscsi_strerror(-rc)); + sock_release(socket); + session->socket = NULL; + } + + return rc; +} + +void +iscsi_disconnect(struct iscsi_session *session) +{ + if (session->socket) { + sock_release(session->socket); + session->socket = NULL; + } +} + +/** + * iscsi_sendpage - Transmit data using sock->ops->sendpage + * @session: iscsi_session to the target + * @flags: MSG_MORE or 0 + * @pg: page to send + * @pg_offset: offset in page + * @len: length of the data to be transmitted. + **/ +int +iscsi_sendpage(struct iscsi_session *session, int flags, struct page *pg, + unsigned int pg_offset, unsigned int len) +{ + struct socket *sock = session->socket; + int rc; + + rc = sock->ops->sendpage(sock, pg, pg_offset, len, flags); + if (signal_pending(current)) + return ISCSI_IO_INTR; + else if (rc != len) { + if (rc == 0) + iscsi_host_err(session, "iscsi_sendpage() failed due " + "to connection closed by target\n"); + else if (rc < 0) + iscsi_host_err(session, "iscsi_sendpage() failed with " + "rc %d: %s\n", rc, iscsi_strerror(-rc)); + else + iscsi_host_err(session, "iscsi_sendpage() failed due " + "to short write of %d of %u\n", rc, + len); + return ISCSI_IO_ERR; + } + + return ISCSI_IO_SUCCESS; +} + +/** + * iscsi_send/recvmsg - recv or send a iSCSI PDU, or portion thereof + * @session: iscsi session + * @iov: contains list of buffers to receive data in + * @iovn: number of buffers in IO vec + * @size: total size of data to be received + * + * Note: + * tcp_*msg() might be interrupted because we got + * sent a signal, e.g. SIGHUP from iscsi_drop_session(). In + * this case, we most likely did not receive all the data, and + * we should just bail out. No need to log any message since + * this is expected behavior. + **/ +int +iscsi_recvmsg(struct iscsi_session *session, struct kvec *iov, size_t iovn, + size_t size) +{ + struct msghdr msg; + int rc; + + memset(&msg, 0, sizeof(msg)); + rc = kernel_recvmsg(session->socket, &msg, iov, iovn, size, + MSG_WAITALL); + if (signal_pending(current)) + return ISCSI_IO_INTR; + else if (rc != size) { + if (rc == 0) + iscsi_host_err(session, "iscsi_recvmsg() failed due " + "to connection closed by target\n"); + else if (rc < 0) + iscsi_host_err(session, "iscsi_recvmsg() failed with " + "rc %d: %s\n", rc, iscsi_strerror(-rc)); + else + iscsi_host_err(session, "iscsi_recvmsg() failed due " + "to short read of %d\n", rc); + return ISCSI_IO_ERR; + } + + return ISCSI_IO_SUCCESS; +} + +int +iscsi_sendmsg(struct iscsi_session *session, struct kvec *iov, size_t iovn, + size_t size) +{ + struct msghdr msg; + int rc; + + memset(&msg, 0, sizeof(msg)); + rc = kernel_sendmsg(session->socket, &msg, iov, iovn, size); + if (signal_pending(current)) + return ISCSI_IO_INTR; + else if (rc != size) { + if (rc == 0) + iscsi_host_err(session, "iscsi_sendmsg() failed due " + "to connection closed by target\n"); + else if (rc < 0) + iscsi_host_err(session, "iscsi_sendmsg() failed with " + "rc %d: %s\n", rc, iscsi_strerror(-rc)); + else + iscsi_host_err(session, "iscsi_sendmsg() failed due " + "to short write of %d\n", rc); + return ISCSI_IO_ERR; + } + + return ISCSI_IO_SUCCESS; +} diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-portal.c linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-portal.c --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-portal.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-portal.c 2005-06-15 17:18:33.387472100 -0500 @@ -0,0 +1,93 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-portal.c,v 1.1.2.11 2005/04/26 17:44:50 mikenc Exp $ + * + * Portal setup functions + */ +#include +#include +#include + +#include "iscsi-session.h" +#include "iscsi-ioctl.h" +#include "iscsi-sfnet.h" + +/* caller must hold the session's portal_lock */ +void +iscsi_set_portal_info(struct iscsi_session *session) +{ + /* + * Set the iSCSI op params based on the portal's + * settings. Don't change the address, since a termporary redirect may + * have already changed the address, and we want to use the redirected + * address rather than the portal's address. + */ + session->initial_r2t = session->portal.initial_r2t; + session->immediate_data = session->portal.immediate_data; + session->max_recv_data_segment_len = + session->portal.max_recv_data_segment_len; + session->first_burst_len = session->portal.first_burst_len; + session->max_burst_len = session->portal.max_burst_len; + session->def_time2wait = session->portal.def_time2wait; + session->def_time2retain = session->portal.def_time2retain; + + session->header_digest = session->portal.header_digest; + session->data_digest = session->portal.data_digest; + + session->portal_group_tag = session->portal.tag; + + /* TCP options */ + session->tcp_window_size = session->portal.tcp_window_size; + /* FIXME: type_of_service */ +} + +/* caller must hold the session's portal_lock */ +void +iscsi_set_portal(struct iscsi_session *session) +{ + /* address */ + memcpy(&session->addr, &session->portal.addr, sizeof(struct sockaddr)); + /* timeouts, operational params, other settings */ + iscsi_set_portal_info(session); +} + +/* + * returns 1 if a relogin is required. + * caller must hold the session's portal_lock + */ +int +iscsi_update_portal_info(struct iscsi_portal_info *old, + struct iscsi_portal_info *new) +{ + int ret = 0; + + if (new->initial_r2t != old->initial_r2t || + new->immediate_data != old->immediate_data || + new->max_recv_data_segment_len != old->max_recv_data_segment_len || + new->first_burst_len != old->first_burst_len || + new->max_burst_len != old->max_burst_len || + new->def_time2wait != old->def_time2wait || + new->def_time2retain != old->def_time2retain || + new->header_digest != old->header_digest || + new->data_digest != old->data_digest || + new->tcp_window_size != old->tcp_window_size) + ret = 1; + + memcpy(old, new, sizeof(*old)); + return ret; +} diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-portal.h linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-portal.h --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-portal.h 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-portal.h 2005-06-15 17:19:56.688824080 -0500 @@ -0,0 +1,57 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-portal.h,v 1.1.2.9 2005/04/26 17:44:50 mikenc Exp $ + * + * portal info structure used in ioctls and the kernel module + */ +#ifndef ISCSI_PORTAL_H_ +#define ISCSI_PORTAL_H_ + +#include + +struct iscsi_session; + +/* + * iscsi_portal_info - contains the values userspace had + * requested. This differs from the session duplicates + * as they are the values we negotiated with the target + */ +struct iscsi_portal_info { + int initial_r2t; + int immediate_data; + int max_recv_data_segment_len; + int first_burst_len; + int max_burst_len; + int def_time2wait; + int def_time2retain; + int header_digest; + int data_digest; + int tag; + int tcp_window_size; + int type_of_service; + /* support ipv4 when we finish the interface */ + struct sockaddr addr; +}; + +extern void iscsi_set_portal_info(struct iscsi_session *session); +extern void iscsi_set_portal(struct iscsi_session *session); +extern int iscsi_update_portal_info(struct iscsi_portal_info *old, + struct iscsi_portal_info *new); +#endif diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-protocol.h linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-protocol.h --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-protocol.h 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-protocol.h 2005-06-15 17:19:56.689823940 -0500 @@ -0,0 +1,55 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-protocol.h,v 1.1.2.8 2005/03/29 19:35:09 mikenc Exp $ + * + * This file sets up definitions of messages and constants used by the + * iSCSI protocol. + */ +#ifndef ISCSI_PROTOCOL_H_ +#define ISCSI_PROTOCOL_H_ + +#include "iscsi.h" + +/* assumes a pointer to a 3-byte array */ +#define ntoh24(p) (((p)[0] << 16) | ((p)[1] << 8) | ((p)[2])) + +/* assumes a pointer to a 3 byte array, and an integer value */ +#define hton24(p, v) {\ + p[0] = (((v) >> 16) & 0xFF); \ + p[1] = (((v) >> 8) & 0xFF); \ + p[2] = ((v) & 0xFF); \ +} + +/* for Login min, max, active version fields */ +#define ISCSI_MIN_VERSION ISCSI_DRAFT20_VERSION +#define ISCSI_MAX_VERSION ISCSI_DRAFT20_VERSION + +/* Padding word length */ +#define PAD_WORD_LEN 4 + +/* maximum length for text values */ +#define TARGET_NAME_MAXLEN 255 + +/* + * We should come up with a enum or some defines (in iscsi.h) + * of all the iSCSI defaults so we can verify values against + * what we receive (from the ioctl and targets) + */ +#define DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH 8192 + +#endif diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-recv-pdu.c linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-recv-pdu.c --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-recv-pdu.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-recv-pdu.c 2005-06-15 17:18:33.388471960 -0500 @@ -0,0 +1,1004 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-recv-pdu.c,v 1.1.2.32 2005/03/29 19:35:08 mikenc Exp $ + * + * All the incoming iSCSI PDUs are processed by functions + * defined here. + */ +#include +#include +#include +#include +#include + +#include "iscsi-session.h" +#include "iscsi-task.h" +#include "iscsi-protocol.h" +#include "iscsi-login.h" +#include "iscsi-sfnet.h" + +/* possibly update the ExpCmdSN and MaxCmdSN - may acquire task lock */ +static void +update_sn(struct iscsi_session *session, u32 expcmdsn, u32 maxcmdsn) +{ + /* + * standard specifies this check for when to update expected and + * max sequence numbers + */ + if (iscsi_sna_lt(maxcmdsn, expcmdsn - 1)) + return; + + if (expcmdsn != session->exp_cmd_sn && + !iscsi_sna_lt(expcmdsn, session->exp_cmd_sn)) + session->exp_cmd_sn = expcmdsn; + + if (maxcmdsn != session->max_cmd_sn && + !iscsi_sna_lt(maxcmdsn, session->max_cmd_sn)) { + session->max_cmd_sn = maxcmdsn; + /* wake the tx thread to try sending more commands */ + iscsi_wake_tx_thread(TX_SCSI_COMMAND, session); + } + + /* + * record whether or not the command window for this session + * has closed, so that we can ping the target periodically to + * ensure we eventually find out that the window has re-opened. + */ + if (maxcmdsn == expcmdsn - 1) { + /* + * record how many times this happens, to see + * how often we're getting throttled + */ + session->window_closed++; + /* + * prepare to poll the target to see if + * the window has reopened + */ + spin_lock_bh(&session->task_lock); + iscsi_mod_session_timer(session, 5); + set_bit(SESSION_WINDOW_CLOSED, &session->control_bits); + spin_unlock_bh(&session->task_lock); + } else if (test_bit(SESSION_WINDOW_CLOSED, &session->control_bits)) + clear_bit(SESSION_WINDOW_CLOSED, &session->control_bits); +} + +static int +iscsi_recv_header(struct iscsi_session *session, struct iscsi_hdr *sth, + int digest) +{ + struct scatterlist sg; + struct kvec iov[2]; + int length, rc; + u32 recvd_crc32c, hdr_crc32c; + u8 iovn = 0; + + iov[iovn].iov_base = sth; + iov[iovn].iov_len = length = sizeof(*sth); + iovn++; + if (digest == ISCSI_DIGEST_CRC32C) { + iov[iovn].iov_base = &recvd_crc32c; + iov[iovn].iov_len = sizeof(recvd_crc32c); + iovn++; + length += sizeof(recvd_crc32c); + } + + rc = iscsi_recvmsg(session, iov, iovn, length); + if (rc != ISCSI_IO_SUCCESS) + return rc; + + if (digest == ISCSI_DIGEST_CRC32C) { + crypto_digest_init(session->rx_tfm); + sg_init_one(&sg, (u8 *)sth, sizeof(*sth)); + crypto_digest_digest(session->rx_tfm, &sg, 1, + (u8*)&hdr_crc32c); + if (recvd_crc32c != hdr_crc32c) { + iscsi_host_err(session, "HeaderDigest mismatch, " + "received 0x%08x, calculated 0x%08x, " + "dropping session\n", recvd_crc32c, + hdr_crc32c); + return ISCSI_IO_CRC32C_ERR; + } + } + + /* connection is ok */ + session->last_rx = jiffies; + + if (sth->hlength) { + /* + * FIXME: read any additional header segments. + * For now, drop the session if one is + * received, since we can't handle them. + */ + iscsi_host_err(session, "Received opcode %x, ahs length %d, itt" + " %u. Dropping, additional header segments not " + "supported by this driver version.\n", + sth->opcode, sth->hlength, ntohl(sth->itt)); + return ISCSI_IO_ERR; + } + + return ISCSI_IO_SUCCESS; +} + +static void +handle_logout(struct iscsi_session *session, struct iscsi_hdr *sth) +{ + struct iscsi_logout_rsp_hdr *stlh = (struct iscsi_logout_rsp_hdr *)sth; + + update_sn(session, ntohl(stlh->expcmdsn), ntohl(stlh->maxcmdsn)); + + if (test_bit(SESSION_IN_LOGOUT, &session->control_bits)) + switch (stlh->response) { + case ISCSI_LOGOUT_SUCCESS: + /* + * set session's time2wait to zero? + * use DefaultTime2Wait? + */ + session->time2wait = 0; + iscsi_host_notice(session, "Session logged out\n"); + break; + case ISCSI_LOGOUT_CID_NOT_FOUND: + iscsi_host_err(session, "Session logout failed, cid not" + " found\n"); + break; + case ISCSI_LOGOUT_RECOVERY_UNSUPPORTED: + iscsi_host_err(session, "Session logout failed, " + "connection recovery not supported\n"); + break; + case ISCSI_LOGOUT_CLEANUP_FAILED: + iscsi_host_err(session, "Session logout failed, cleanup" + " failed\n"); + break; + default: + iscsi_host_err(session, "Session logout failed, " + "response 0x%x\n", stlh->response); + break; + } + else + iscsi_host_err(session, "Session received logout response, but " + "never sent a login request\n"); + iscsi_drop_session(session); +} + +static void +setup_nop_out(struct iscsi_session *session, struct iscsi_nop_in_hdr *stnih) +{ + struct iscsi_nop_info *nop_info; + + /* + * we preallocate space for one data-less nop reply in + * session structure, to avoid having to invoke kernel + * memory allocator in the common case where the target + * has at most one outstanding data-less nop reply + * requested at any given time. + */ + spin_lock_bh(&session->task_lock); + if (session->nop_reply.ttt == ISCSI_RSVD_TASK_TAG && + list_empty(&session->nop_reply_list)) + nop_info = &session->nop_reply; + else { + nop_info = kmalloc(sizeof(*nop_info), GFP_ATOMIC); + if (!nop_info) { + spin_unlock_bh(&session->task_lock); + iscsi_host_warn(session, "Couldn't queue nop reply " + "for ttt %u ", ntohl(stnih->ttt)); + return; + } + list_add_tail(&nop_info->reply_list, &session->nop_reply_list); + } + + session->nop_reply.ttt = stnih->ttt; + memcpy(session->nop_reply.lun, stnih->lun, + sizeof(session->nop_reply.lun)); + spin_unlock_bh(&session->task_lock); + + iscsi_wake_tx_thread(TX_NOP_REPLY, session); +} + +static void +handle_nop_in(struct iscsi_session *session, struct iscsi_hdr *sth) +{ + struct iscsi_nop_in_hdr *stnih = (struct iscsi_nop_in_hdr *)sth; + + update_sn(session, ntohl(stnih->expcmdsn), ntohl(stnih->maxcmdsn)); + + if (stnih->itt != ISCSI_RSVD_TASK_TAG) + /* + * we do not send data in our nop-outs, so there + * is not much to do right now + */ + + /* + * FIXME: check StatSN + */ + session->exp_stat_sn = ntohl(stnih->statsn) + 1; + + /* + * check the ttt to decide whether to reply with a Nop-out + */ + if (stnih->ttt != ISCSI_RSVD_TASK_TAG) + setup_nop_out(session, stnih); +} + +/** + * handle_scsi_rsp - Process the SCSI response PDU. + * @session: Session on which the cmd response is received. + * @stsrh: SCSI cmd Response header + * @sense_data: Sense data received for the cmd + * + * Description: + * Get the task for the SCSI cmd, process the response received and + * complete the task. + **/ +static void +handle_scsi_rsp(struct iscsi_session *session, struct iscsi_hdr *sth, + unsigned char *sense_data) +{ + struct iscsi_scsi_rsp_hdr *stsrh = (struct iscsi_scsi_rsp_hdr *)sth; + struct iscsi_task *task; + unsigned int senselen = 0; + u32 itt = ntohl(stsrh->itt); + + /* FIXME: check StatSN */ + session->exp_stat_sn = ntohl(stsrh->statsn) + 1; + update_sn(session, ntohl(stsrh->expcmdsn), ntohl(stsrh->maxcmdsn)); + + spin_lock_bh(&session->task_lock); + task = iscsi_find_session_task(session, itt); + if (!task) { + iscsi_host_info(session, "recv_cmd - response for itt %u, but " + "no such task\n", itt); + spin_unlock_bh(&session->task_lock); + return; + } + + /* check for sense data */ + if (ntoh24(stsrh->dlength) > 1) { + /* + * Sense data format per draft-08, 3.4.6. 2-byte sense length, + * then sense data, then iSCSI response data + */ + senselen = (sense_data[0] << 8) | sense_data[1]; + if (senselen > (ntoh24(stsrh->dlength) - 2)) + senselen = (ntoh24(stsrh->dlength) - 2); + sense_data += 2; + } + + iscsi_process_task_response(task, stsrh, sense_data, senselen); + iscsi_complete_task(task); + __iscsi_put_task(task); + spin_unlock_bh(&session->task_lock); +} + +static void +handle_r2t(struct iscsi_session *session, struct iscsi_hdr *sth) +{ + struct iscsi_r2t_hdr *strh = (struct iscsi_r2t_hdr *)sth; + struct iscsi_task *task; + u32 itt = ntohl(strh->itt); + + update_sn(session, ntohl(strh->expcmdsn), ntohl(strh->maxcmdsn)); + + spin_lock_bh(&session->task_lock); + + task = iscsi_find_session_task(session, itt); + if (!task) { + /* the task no longer exists */ + iscsi_host_info(session, "ignoring R2T for itt %u, %u bytes @ " + "offset %u\n", ntohl(strh->itt), + ntohl(strh->data_length), + ntohl(strh->data_offset)); + goto done; + } + + if (!test_bit(ISCSI_TASK_WRITE, &task->flags)) { + /* + * bug in the target. the command isn't a write, + * so we have no data to send + */ + iscsi_host_err(session, "Ignoring unexpected R2T for task itt " + "%u, %u bytes @ offset %u, ttt %u, not a write " + "command\n", ntohl(strh->itt), + ntohl(strh->data_length), + ntohl(strh->data_offset), ntohl(strh->ttt)); + iscsi_drop_session(session); + } else if (task->ttt != ISCSI_RSVD_TASK_TAG) + /* + * bug in the target. MaxOutstandingR2T == 1 should + * have prevented this from occuring + */ + iscsi_host_warn(session, "Ignoring R2T for task itt %u, %u " + "bytes @ offset %u, ttt %u, already have R2T " + "for %u @ %u, ttt %u\n", ntohl(strh->itt), + ntohl(strh->data_length), + ntohl(strh->data_offset), ntohl(strh->ttt), + task->data_length, task->data_offset, + ntohl(task->ttt)); + else { + /* record the R2T */ + task->ttt = strh->ttt; + task->data_length = ntohl(strh->data_length); + task->data_offset = ntohl(strh->data_offset); + /* + * even if we've issued an abort task set, we need + * to respond to R2Ts for this task, though we can + * apparently set the F-bit and terminate the data burst + * early. Rather than hope targets handle that + * correctly, we just send the data requested as usual. + */ + iscsi_queue_r2t(session, task); + iscsi_wake_tx_thread(TX_DATA, session); + } + + __iscsi_put_task(task); + + done: + spin_unlock_bh(&session->task_lock); +} + +static int +recv_extra_data(struct iscsi_session *session, u32 data_len, u32 *recvd_crc32c) +{ + struct scatterlist tmpsg; + struct kvec iov[2]; + char padding[PAD_WORD_LEN - 1]; + int pad = 0, iovn = 0, len = 0, rc; + + if (data_len % PAD_WORD_LEN) { + pad = PAD_WORD_LEN - (data_len % PAD_WORD_LEN); + iov[iovn].iov_base = padding; + iov[iovn].iov_len = pad; + iovn++; + len += pad; + } + + if (recvd_crc32c) { + iov[iovn].iov_base = recvd_crc32c; + iov[iovn].iov_len = sizeof(*recvd_crc32c); + len += iov[iovn].iov_len; + iovn++; + } + + if (iovn) { + rc = iscsi_recvmsg(session, iov, iovn, len); + if (rc != ISCSI_IO_SUCCESS) + return rc; + + if (pad && recvd_crc32c) { + sg_init_one(&tmpsg, padding, pad); + crypto_digest_update(session->rx_tfm, &tmpsg, 1); + } + } + + return ISCSI_IO_SUCCESS; +} + +/** + * iscsi_recv_sg_data - read the PDU's payload + * @session: iscsi session + * @data_len: data length + * @sglist: data scatterlist + * @sglist_len: number of sg elements + * @sg_offset: offset in sglist + * @digest_opt: CRC32C or NONE + **/ +static int +iscsi_recv_sg_data(struct iscsi_session *session, u32 data_len, + struct scatterlist *sglist, int sglist_len, + unsigned int sg_offset, int digest_opt) +{ + int i, len, rc = ISCSI_IO_ERR; + struct scatterlist *sg, tmpsg; + unsigned int page_offset, remaining, sg_bytes; + struct page *p; + void *page_addr; + struct kvec iov; + u32 recvd_crc32c, data_crc32c; + + remaining = data_len; + + if (digest_opt == ISCSI_DIGEST_CRC32C) + crypto_digest_init(session->rx_tfm); + /* + * Read in the data for each sg in PDU + */ + for (i = 0; remaining > 0 && i < sglist_len; i++) { + /* + * Find the right sg entry first + */ + if (sg_offset >= sglist[i].length) { + sg_offset -= sglist[i].length; + continue; + } + sg = &sglist[i]; + + /* + * Find page corresponding to segment offset first + */ + page_offset = sg->offset + sg_offset; + p = sg->page + (page_offset >> PAGE_SHIFT); + page_offset -= (page_offset & PAGE_MASK); + /* + * yuck, for each page in sg (can't pass a sg with its + * pages mapped to kernel_recvmsg in one iov entry and must + * use one iov entry for each PAGE when using highmem???????) + */ + sg_bytes = min(remaining, sg->length - sg_offset); + remaining -= sg_bytes; + for (; sg_bytes > 0; sg_bytes -= len) { + page_addr = kmap(p); + if (!page_addr) { + iscsi_host_err(session, "recv_sg_data kmap " + "failed to map page in sg %p\n", + sg); + goto error_exit; + } + + iov.iov_base = page_addr + page_offset; + iov.iov_len = min_t(unsigned int, sg_bytes, + PAGE_SIZE - page_offset); + len = iov.iov_len; + /* + * is it better to do one call with all the pages + * setup or multiple calls? + */ + rc = iscsi_recvmsg(session, &iov, 1, len); + kunmap(p); + if (rc != ISCSI_IO_SUCCESS) + goto error_exit; + + /* crypto_digest_update will kmap itself */ + if (digest_opt == ISCSI_DIGEST_CRC32C) { + tmpsg.page = p; + tmpsg.offset = page_offset; + tmpsg.length = len; + crypto_digest_update(session->rx_tfm, &tmpsg, + 1); + } + + p++; + page_offset = 0; + } + + sg_offset = 0; + } + + if (remaining != 0) { + /* Maybe this should be a BUG? */ + iscsi_host_err(session, "recv_sg_data - invalid sglist for " + "offset %u len %u, remaining data %u, sglist " + "size %d, dropping session\n", sg_offset, + data_len, remaining, sglist_len); + goto error_exit; + } + + rc = recv_extra_data(session, data_len, digest_opt == + ISCSI_DIGEST_CRC32C ? &recvd_crc32c : NULL); + if (rc != ISCSI_IO_SUCCESS) + goto error_exit; + + if (digest_opt == ISCSI_DIGEST_CRC32C) { + crypto_digest_final(session->rx_tfm, (u8*)&data_crc32c); + if (data_crc32c != recvd_crc32c) { + iscsi_host_err(session, "DataDigest mismatch, received " + "0x%08x, calculated 0x%08x\n", + recvd_crc32c, data_crc32c); + return ISCSI_IO_CRC32C_ERR; + } + } + + /* connection is ok */ + session->last_rx = jiffies; + return rc; + + error_exit: + /* FIXME: we could discard the data or drop the session */ + return rc; +} + +/* + * Only call this from recvs where the rx_buffer is not in + * use. We don't bother checking the CRC, since we couldn't + * retry the command anyway + */ +static void +drop_data(struct iscsi_session *session, struct iscsi_hdr *sth) +{ + int pad, length, num_bytes; + struct kvec iov; + + length = ntoh24(sth->dlength); + + pad = length % PAD_WORD_LEN; + if (pad) + pad = PAD_WORD_LEN - pad; + length += pad; + + if (session->data_digest == ISCSI_DIGEST_CRC32C) { + iscsi_host_info(session, "recv_data discarding %d data PDU " + "bytes, %d pad bytes, %Zu digest bytes\n", + ntoh24(sth->dlength), pad, sizeof(u32)); + length += sizeof(u32); + } else + iscsi_host_info(session, "recv_data discarding %d data PDU " + "bytes, %d pad bytes\n", ntoh24(sth->dlength), + pad); + + while (!signal_pending(current) && length > 0) { + num_bytes = min_t(int, length, sizeof(session->rx_buffer)); + iov.iov_base = session->rx_buffer; + iov.iov_len = sizeof(session->rx_buffer); + /* should iov_len match num_bytes ? */ + if (iscsi_recvmsg(session, &iov, 1, num_bytes) != + ISCSI_IO_SUCCESS) { + iscsi_drop_session(session); + break; + } + /* assume a PDU round-trip, connection is ok */ + session->last_rx = jiffies; + length -= num_bytes; + } +} + +static void +handle_scsi_data(struct iscsi_session *session, struct iscsi_hdr *sth) +{ + struct iscsi_data_rsp_hdr *stdrh = (struct iscsi_data_rsp_hdr *)sth; + struct iscsi_task *task; + struct scsi_cmnd *sc; + struct scatterlist sg; + int dlength, offset, rc; + u32 itt = ntohl(stdrh->itt); + + if (stdrh->flags & ISCSI_FLAG_DATA_STATUS) + /* FIXME: check StatSN */ + session->exp_stat_sn = ntohl(stdrh->statsn) + 1; + + update_sn(session, ntohl(stdrh->expcmdsn), ntohl(stdrh->maxcmdsn)); + + dlength = ntoh24(stdrh->dlength); + offset = ntohl(stdrh->offset); + + spin_lock_bh(&session->task_lock); + + task = iscsi_find_session_task(session, itt); + if (!task) { + iscsi_host_warn(session, "recv_data, no task for itt %u next " + "itt %u, discarding received data, offset %u " + "len %u\n", ntohl(stdrh->itt), + session->next_itt, offset, dlength); + spin_unlock_bh(&session->task_lock); + drop_data(session, sth); + return; + } + sc = task->scsi_cmnd; + + /* sanity check the PDU against the command */ + if (!test_bit(ISCSI_TASK_READ, &task->flags)) { + iscsi_host_err(session, "lun%u: recv_data itt %u, command " + "cdb 0x%02x, dropping session due to " + "unexpected Data-in from\n", task->lun, itt, + sc->cmnd[0]); + iscsi_drop_session(session); + goto done; + } else if ((offset + dlength) > sc->request_bufflen) { + /* buffer overflow, often because of a corrupt PDU header */ + iscsi_host_err(session, "recv_data for itt %u, cmnd 0x%x, " + "bufflen %u, Data PDU with offset %u len %u " + "overflows command buffer, dropping session\n", + itt, sc->cmnd[0], sc->request_bufflen, offset, + dlength); + iscsi_drop_session(session); + goto done; + } else if (task->rxdata != offset) { + /* + * if the data arrives out-of-order, it becomes much harder + * for us to correctly calculate the residual if we don't get + * enough data and also don't get an underflow from the + * target. This can happen if we discard Data PDUs due to + * bogus offsets/lengths. Since we always negotiate for + * Data PDUs in-order, this should never happen, but check + * for it anyway. + */ + iscsi_host_err(session, "recv_data for itt %u, cmnd 0x%x, " + "bufflen %u, offset %u does not match expected " + "offset %u, dropping session\n", itt, + sc->cmnd[0], sc->request_bufflen, offset, + task->rxdata); + iscsi_drop_session(session); + goto done; + } + + /* + * either we'll read it all, or we'll drop the session and requeue + * the command, so it's safe to increment early + */ + task->rxdata += dlength; + spin_unlock_bh(&session->task_lock); + + if (sc->use_sg) + rc = iscsi_recv_sg_data(session, dlength, sc->request_buffer, + sc->use_sg, offset, + session->data_digest); + else { + sg_init_one(&sg, sc->request_buffer, dlength); + rc = iscsi_recv_sg_data(session, dlength, &sg, 1, offset, + session->data_digest); + } + + spin_lock_bh(&session->task_lock); + + switch (rc) { + case ISCSI_IO_ERR: + iscsi_drop_session(session); + break; + case ISCSI_IO_CRC32C_ERR: + __set_bit(ISCSI_TASK_CRC_ERROR, &task->flags); + /* fall through */ + case ISCSI_IO_SUCCESS: + if (stdrh->flags & ISCSI_FLAG_DATA_STATUS) { + iscsi_process_task_status(task, sth); + iscsi_complete_task(task); + } + } + + done: + __iscsi_put_task(task); + spin_unlock_bh(&session->task_lock); +} + +/** + * handle_task_mgmt_rsp - Process the task management response. + * @session: to retrieve the task + * @ststmrh: task management response header + * + * Description: + * Retrieve the task for which task mgmt response is received and take + * appropriate action based on the type of task management request. + **/ +static void +handle_task_mgmt_rsp(struct iscsi_session *session, struct iscsi_hdr *sth) +{ + struct iscsi_scsi_task_mgmt_rsp_hdr *ststmrh; + struct iscsi_task *task; + u32 mgmt_itt; + + ststmrh = (struct iscsi_scsi_task_mgmt_rsp_hdr *)sth; + mgmt_itt = ntohl(ststmrh->itt); + + /* FIXME: check StatSN */ + session->exp_stat_sn = ntohl(ststmrh->statsn) + 1; + update_sn(session, ntohl(ststmrh->expcmdsn), ntohl(ststmrh->maxcmdsn)); + + spin_lock_bh(&session->task_lock); + /* + * This can fail if they timedout and we escalated the recovery + * to a new function + */ + task = iscsi_find_session_task(session, mgmt_itt); + if (!task) { + iscsi_host_warn(session, "mgmt response 0x%x for unknown itt " + "%u, rtt %u\n", ststmrh->response, + ntohl(ststmrh->itt), ntohl(ststmrh->rtt)); + goto done; + } + + if (ststmrh->response == 0) { + iscsi_host_info(task->session, "task mgmt itt %u " + "successful\n", mgmt_itt); + iscsi_complete_tmf_task(task, ISCSI_TASK_TMF_SUCCESS); + } else { + iscsi_host_err(task->session, "task mgmt itt %u rejected" + " (0x%x)\n", mgmt_itt, ststmrh->response); + iscsi_complete_tmf_task(task, ISCSI_TASK_TMF_FAILED); + } + __iscsi_put_task(task); + + done: + /* + * we got the expected response, allow the eh thread to send + * another task mgmt PDU whenever it wants to + */ + if (session->last_mgmt_itt == mgmt_itt) + session->last_mgmt_itt = ISCSI_RSVD_TASK_TAG; + + spin_unlock_bh(&session->task_lock); +} + +static void +process_immed_cmd_reject(struct iscsi_session *session, unsigned char *xbuf, + int dlength) +{ + u32 itt; + struct iscsi_task *task; + struct iscsi_hdr pdu; + + if (dlength < sizeof(pdu)) { + iscsi_host_warn(session, "Immediate command rejected, dlength " + "%u\n", dlength); + return; + } + + /* look at the rejected PDU */ + memcpy(&pdu, xbuf, sizeof(pdu)); + itt = ntohl(pdu.itt); + + /* + * try to find the task corresponding to this itt, + * and wake up any process waiting on it + */ + spin_lock_bh(&session->task_lock); + + if (session->last_mgmt_itt == itt) + session->last_mgmt_itt = ISCSI_RSVD_TASK_TAG; + + task = iscsi_find_session_task(session, itt); + if (task) { + iscsi_host_notice(session, "task mgmt PDU rejected, mgmt %u, " + "itt %u\n", itt, task->itt); + iscsi_complete_tmf_task(task, ISCSI_TASK_IMM_REJECT); + __iscsi_put_task(task); + } else if ((pdu.opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_LOGOUT_CMD) + /* + * our Logout was rejected. just let the + * logout response timer drop the session + */ + iscsi_host_warn(session, "Logout PDU rejected, itt %u\n", itt); + else + iscsi_host_warn(session, "itt %u immediate command rejected\n", + itt); + + spin_unlock_bh(&session->task_lock); +} + +static void +handle_reject(struct iscsi_session *session, struct iscsi_hdr *sth, + unsigned char *xbuf) +{ + struct iscsi_reject_hdr *reject; + struct iscsi_hdr pdu; + int dlength; + u32 itt; + + reject = (struct iscsi_reject_hdr *)sth; + dlength = ntoh24(reject->dlength); + + /* FIXME: check StatSN */ + session->exp_stat_sn = ntohl(reject->statsn) + 1; + update_sn(session, ntohl(reject->expcmdsn), ntohl(reject->maxcmdsn)); + + if (reject->reason == ISCSI_REJECT_DATA_DIGEST_ERROR) { + /* + * we don't need to do anything about these, + * timers or other PDUs will handle the problem. + */ + if (dlength >= sizeof(pdu)) { + memcpy(&pdu, xbuf, sizeof(pdu)); + itt = ntohl(pdu.itt); + iscsi_host_warn(session, "itt %u (opcode 0x%x) rejected" + " because of a DataDigest error\n", itt, + pdu.opcode); + } else + iscsi_host_warn(session, "Target rejected a PDU because" + " of a DataDigest error\n"); + } else if (reject->reason == ISCSI_REJECT_IMM_CMD_REJECT) + process_immed_cmd_reject(session, xbuf, dlength); + else { + if (dlength >= sizeof(pdu)) { + /* look at the rejected PDU */ + memcpy(&pdu, xbuf, sizeof(pdu)); + itt = ntohl(pdu.itt); + iscsi_host_err(session, "Dropping session because " + "target rejected a PDU, reason 0x%x, " + "dlength %d, rejected itt %u, opcode " + "0x%x\n", reject->reason, dlength, itt, + pdu.opcode); + } else + iscsi_host_err(session, "Dropping session because " + "target rejected a PDU, reason 0x%x, " + "dlength %u\n", reject->reason, dlength); + iscsi_drop_session(session); + } +} + +static void +handle_async_msg(struct iscsi_session *session, struct iscsi_hdr *sth, + unsigned char *xbuf) +{ + struct iscsi_async_msg_hdr *staeh = (struct iscsi_async_msg_hdr *)sth; + unsigned int senselen; + + /* FIXME: check StatSN */ + session->exp_stat_sn = ntohl(staeh->statsn) + 1; + update_sn(session, ntohl(staeh->expcmdsn), ntohl(staeh->maxcmdsn)); + + switch (staeh->async_event) { + case ISCSI_ASYNC_MSG_SCSI_EVENT: + senselen = (xbuf[0] << 8) | xbuf[1]; + xbuf += 2; + + iscsi_host_info(session, "Received async SCSI event. Printing " + "sense\n"); +/* + remove for 2.6.11 + __scsi_print_sense(ISCSI_PROC_NAME, xbuf, senselen); +*/ + break; + case ISCSI_ASYNC_MSG_REQUEST_LOGOUT: + /* + * FIXME: this is really a request to drop a connection, + * not the whole session, but we currently only have one + * connection per session, so there's no difference + * at the moment. + */ + iscsi_host_warn(session, "Target requests logout within %u " + "seconds for session\n", ntohs(staeh->param3)); + /* + * we need to get the task lock to make sure the TX thread + * isn't in the middle of adding another task to the session. + */ + spin_lock_bh(&session->task_lock); + iscsi_request_logout(session, ntohs(staeh->param3) - (HZ / 10), + session->active_timeout); + spin_unlock_bh(&session->task_lock); + break; + case ISCSI_ASYNC_MSG_DROPPING_CONNECTION: + iscsi_host_warn(session, "Target dropping connection %u, " + "reconnect min %u max %u\n", + ntohs(staeh->param1), ntohs(staeh->param2), + ntohs(staeh->param3)); + session->time2wait = (long) ntohs(staeh->param2) & 0x0000FFFFFL; + break; + case ISCSI_ASYNC_MSG_DROPPING_ALL_CONNECTIONS: + iscsi_host_warn(session, "Target dropping all connections, " + "reconnect min %u max %u\n", + ntohs(staeh->param2), ntohs(staeh->param3)); + session->time2wait = (long) ntohs(staeh->param2) & 0x0000FFFFFL; + break; + case ISCSI_ASYNC_MSG_VENDOR_SPECIFIC: + iscsi_host_warn(session, "Ignoring vendor-specific async event," + " vcode 0x%x\n", staeh->async_vcode); + break; + case ISCSI_ASYNC_MSG_PARAM_NEGOTIATION: + iscsi_host_warn(session, "Received async event param " + "negotiation, dropping session\n"); + iscsi_drop_session(session); + break; + default: + iscsi_host_err(session, "Received unknown async event 0x%x\n", + staeh->async_event); + break; + } + if (staeh->async_event == ISCSI_ASYNC_MSG_DROPPING_CONNECTION || + staeh->async_event == ISCSI_ASYNC_MSG_DROPPING_ALL_CONNECTIONS || + staeh->async_event == ISCSI_ASYNC_MSG_REQUEST_LOGOUT) { + spin_lock(&session->portal_lock); + memcpy(&session->addr, &session->portal.addr, + sizeof(struct sockaddr)); + spin_unlock(&session->portal_lock); + } +} + +/** + * iscsi_recv_pdu - Read in a iSCSI PDU + * @session: iscsi session structure + * @hdr: a iSCSI PDU header + * @hdr_digest: digest type for header + * @data: buffer for data + * @max_data_len: buffer size + * @data_digest: digest type for data + * + * Description: + * Reads a iSCSI PDU into memory. Excpet for login PDUs, this function + * will also process the PDU. + **/ +int +iscsi_recv_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr, + int hdr_digest, char *data, int max_data_len, int data_digest) +{ + int rc; + int data_len; + struct scatterlist sg; + + if (iscsi_recv_header(session, hdr, hdr_digest) != ISCSI_IO_SUCCESS) + goto fail; + + data_len = ntoh24(hdr->dlength); + /* + * scsi data is read in and processed by its handler for now + */ + if (data_len && hdr->opcode != ISCSI_OP_SCSI_DATA_RSP) { + if (data_len > max_data_len) { + iscsi_host_err(session, "iscsi_recv_pdu() cannot read " + "%d bytes of PDU data, only %d bytes " + "of buffer available\n", data_len, + max_data_len); + goto fail; + } + + /* + * must clear this, beucase the login api uses the same + * buffer for recv and send + */ + memset(data, 0, max_data_len); + sg_init_one(&sg, data, data_len); + rc = iscsi_recv_sg_data(session, data_len, &sg, 1, 0, + data_digest); + if (rc == ISCSI_IO_CRC32C_ERR) { + switch (hdr->opcode) { + case ISCSI_OP_ASYNC_MSG: + case ISCSI_OP_REJECT: + /* unsolicited so ignore */ + goto done; + default: + goto fail; + }; + } else if (rc != ISCSI_IO_SUCCESS) + goto fail; + } + + switch (hdr->opcode) { + case ISCSI_OP_NOOP_IN: + handle_nop_in(session, hdr); + break; + case ISCSI_OP_SCSI_RSP: + handle_scsi_rsp(session, hdr, data); + break; + case ISCSI_OP_SCSI_TASK_MGT_RSP: + handle_task_mgmt_rsp(session, hdr); + break; + case ISCSI_OP_R2T: + handle_r2t(session, hdr); + break; + case ISCSI_OP_SCSI_DATA_RSP: + handle_scsi_data(session, hdr); + break; + case ISCSI_OP_ASYNC_MSG: + handle_async_msg(session, hdr, data); + break; + case ISCSI_OP_REJECT: + handle_reject(session, hdr, data); + break; + case ISCSI_OP_LOGOUT_RSP: + handle_logout(session, hdr); + break; + case ISCSI_OP_LOGIN_RSP: + /* + * The login api needs the buffer to be cleared when no + * data has been read + */ + if (!data_len) + memset(data, 0, max_data_len); + /* + * login api will process further + */ + break; + default: + iscsi_host_err(session, "Dropping session after receiving " + "unexpected opcode 0x%x\n", hdr->opcode); + session->time2wait = 2; + goto fail; + } + + done: + return 1; + fail: + iscsi_drop_session(session); + return 0; +} diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-session.c linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-session.c --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-session.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-session.c 2005-06-15 17:18:33.388471960 -0500 @@ -0,0 +1,1686 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-session.c,v 1.1.2.34 2005/04/26 17:44:50 mikenc Exp $ + * + * This File implements the funtions related to establishing and + * managing the session. + */ +#include +#include +#include +#include +#include +#include + +#include "iscsi-session.h" +#include "iscsi-ioctl.h" +#include "iscsi-task.h" +#include "iscsi-login.h" +#include "iscsi-sfnet.h" + +/* + * list of initialized iscsi sessions - this should be replaced + * with a driver model equivalent if possible. + */ +LIST_HEAD(iscsi_sessions); +static DECLARE_MUTEX(iscsi_session_sem); + +static void +signal_iscsi_threads(struct iscsi_session *session) +{ + if (session->tx_task) + kill_proc(session->tx_task->pid, SIGHUP, 1); + if (session->rx_task) + kill_proc(session->rx_task->pid, SIGHUP, 1); +} + +/* drop an iscsi session */ +void +iscsi_drop_session(struct iscsi_session *session) +{ + if (!test_and_clear_bit(SESSION_ESTABLISHED, &session->control_bits)) + return; + + /* so we know whether to abort the connection */ + session->session_drop_time = jiffies ? jiffies : 1; + signal_iscsi_threads(session); +} + +void +iscsi_update_replacement_timeout(struct iscsi_session *session, int timeout) +{ + if (timeout < 0) { + iscsi_host_err(session, "Cannot set negative timeout value of" + "%d\n", timeout); + return; + } + + spin_lock(&session->portal_lock); + if (timeout == session->replacement_timeout) { + spin_unlock(&session->portal_lock); + return; + } + + del_timer_sync(&session->replacement_timer); + session->replacement_timeout = timeout; + spin_lock_bh(&session->task_lock); + if ((test_bit(SESSION_ESTABLISHED, &session->control_bits)) || + (test_bit(SESSION_REPLACEMENT_TIMEDOUT, &session->control_bits)) || + !timeout) { + spin_unlock_bh(&session->task_lock); + spin_unlock(&session->portal_lock); + return; + } + spin_unlock_bh(&session->task_lock); + mod_timer(&session->replacement_timer, jiffies + (timeout * HZ)); + spin_unlock(&session->portal_lock); +} + +static void +handle_logout_timeouts(unsigned long data) +{ + struct iscsi_session *session = (struct iscsi_session *)data; + + if (test_bit(SESSION_TERMINATED, &session->control_bits) || + !test_bit(SESSION_LOGOUT_REQUESTED, &session->control_bits)) + return; + /* + * we're waiting for tasks to complete before logging out. No need to + * check the CmdSN window, since we won't be starting any more tasks. + */ + if (test_and_set_bit(SESSION_IN_LOGOUT, &session->control_bits)) { + /* + * passed the deadline for a logout response, just drop the + * session + */ + iscsi_host_err(session, "Logout response timed out, dropping " + "session\n"); + iscsi_drop_session(session); + } else { + iscsi_wake_tx_thread(TX_LOGOUT, session); + mod_timer(&session->logout_timer, + jiffies + (session->logout_response_timeout * HZ)); + } + +} + +/* caller must hold session->task_lock */ +void +iscsi_request_logout(struct iscsi_session *session, int logout_timeout, + int logout_response_timeout) +{ + int timeout; + + if (!test_bit(SESSION_ESTABLISHED, &session->control_bits) || + test_and_set_bit(SESSION_LOGOUT_REQUESTED, &session->control_bits)) + return; + /* + * we should not be sending any new requests, so we do not want + * the net timer to send pings. If we have active tasks then + * we delay logout, but one way or another this session is going + * so we do not need the net timer even if the transport is bad. + */ + del_timer(&session->transport_timer); + + session->logout_response_timeout = logout_response_timeout; + if (session->num_active_tasks == 0) { + timeout = session->logout_response_timeout; + set_bit(SESSION_IN_LOGOUT, &session->control_bits); + iscsi_wake_tx_thread(TX_LOGOUT, session); + } else + timeout = logout_timeout; + mod_timer(&session->logout_timer, jiffies + (timeout * HZ)); +} + +/* + * return value: + * 1: login successfully. + * -1: Failed to login. Retry. + */ +static int +login_response_status(struct iscsi_session *session, + enum iscsi_login_status login_status) +{ + int ret; + + switch (login_status) { + case LOGIN_OK: + /* check the status class and detail */ + ret = 1; + break; + case LOGIN_IO_ERROR: + case LOGIN_WRONG_PORTAL_GROUP: + case LOGIN_REDIRECTION_FAILED: + iscsi_disconnect(session); + ret = -1; + break; + default: + iscsi_disconnect(session); + /* + * these are problems that will probably occur with any portal + * of this target. + */ + ret = -1; + } + + return ret; +} + +/* + * return value: + * 2: login successfully. + * 1: Redirected. Retry login. + * 0: Failed to login. No need to retry. Give up. + * -1: Failed to login. Retry. + */ +static int +check_iscsi_status_class(struct iscsi_session *session, u8 status_class, + u8 status_detail) +{ + switch (status_class) { + case ISCSI_STATUS_CLS_SUCCESS: + return 2; + case ISCSI_STATUS_CLS_REDIRECT: + switch (status_detail) { + case ISCSI_LOGIN_STATUS_TGT_MOVED_TEMP: + return 1; /* not really success, but we want to + * retry immediately, with no delay + */ + case ISCSI_LOGIN_STATUS_TGT_MOVED_PERM: + /* + * for a permanent redirect, we need to update the + * portal address, and then try again. + */ + spin_lock(&session->portal_lock); + /* reset the address in the current portal info */ + memcpy(&session->portal.addr, &session->addr, + sizeof(struct sockaddr)); + spin_unlock(&session->portal_lock); + return 1; /* not really success, but we want to + * retry immediately, with no delay + */ + default: + iscsi_host_err(session, "Login rejected: redirection " + "type 0x%x not supported\n", + status_detail); + iscsi_disconnect(session); + return -1; + } + case ISCSI_STATUS_CLS_INITIATOR_ERR: + iscsi_disconnect(session); + + switch (status_detail) { + case ISCSI_LOGIN_STATUS_AUTH_FAILED: + iscsi_host_err(session, "Login rejected: Initiator " + "failed authentication with target\n"); + return 0; + case ISCSI_LOGIN_STATUS_TGT_FORBIDDEN: + iscsi_host_err(session, "Login rejected: initiator " + "failed authorization with target\n"); + return 0; + case ISCSI_LOGIN_STATUS_TGT_NOT_FOUND: + iscsi_host_err(session, "Login rejected: initiator " + "error - target not found (%02x/%02x)\n", + status_class, status_detail); + return 0; + case ISCSI_LOGIN_STATUS_NO_VERSION: + /* + * FIXME: if we handle multiple protocol versions, + * before we log an error, try the other supported + * versions. + */ + iscsi_host_err(session, "Login rejected: incompatible " + "version (%02x/%02x), non-retryable, " + "giving up\n", status_class, + status_detail); + return 0; + default: + iscsi_host_err(session, "Login rejected: initiator " + "error (%02x/%02x), non-retryable, " + "giving up\n", status_class, + status_detail); + return 0; + } + case ISCSI_STATUS_CLS_TARGET_ERR: + iscsi_host_err(session, "Login rejected: target error " + "(%02x/%02x)\n", status_class, status_detail); + iscsi_disconnect(session); + /* + * We have no idea what the problem is. But spec says initiator + * may retry later. + */ + return -1; + default: + iscsi_host_err(session, "Login response with unknown status " + "class 0x%x, detail 0x%x\n", status_class, + status_detail); + iscsi_disconnect(session); + return 0; + } +} + +static void +login_timed_out(unsigned long data) +{ + struct iscsi_session *session = (struct iscsi_session *)data; + + iscsi_host_err(session, "Login phase timed out, timeout was set for " + "%d secs\n", session->login_timeout); + kill_proc(session->rx_task->pid, SIGHUP, 1); +} + +/** + * iscsi_update_login_timeout - update the login timeout and timer + * @session: iscsi session + * @timeout: new timeout + * + * Notes: + * If it is a pending timer then we restart with the new value. + * And if there was no previous timeout, and a new value + * we start up the timer with the new value. + */ +void +iscsi_update_login_timeout(struct iscsi_session *session, int timeout) +{ + if (timeout < 0) { + iscsi_host_err(session, "Cannot set negative timeout value of" + "%d\n", timeout); + return; + } + + spin_lock(&session->portal_lock); + if (session->login_timeout == timeout) + goto done; + + if ((del_timer(&session->login_timer) && timeout) || + (!session->login_timeout && timeout && + test_bit(SESSION_IN_LOGIN, &session->control_bits))) + mod_timer(&session->login_timer, jiffies + (timeout * HZ)); + session->login_timeout = timeout; + done: + spin_unlock(&session->portal_lock); +} + +static int +__establish_session(struct iscsi_session *session) +{ + int ret = -1; + u8 status_class; + u8 status_detail; + enum iscsi_login_status login_status; + + if (signal_pending(current)) + flush_signals(current); + + iscsi_disconnect(session); + + spin_lock(&session->portal_lock); + /* + * Set almost everything based on the portal's settings. + * Don't change the address, since a temporary redirect + * may have already changed the address, + * and we want to use the redirected address rather than + * the portal's address. + */ + iscsi_set_portal_info(session); + + set_bit(SESSION_IN_LOGIN, &session->control_bits); + if (session->login_timeout) + mod_timer(&session->login_timer, + jiffies + (session->login_timeout * HZ)); + spin_unlock(&session->portal_lock); + + if (iscsi_connect(session)) { + iscsi_host_err(session, "establish_session failed. Could not " + "connect to target\n"); + goto done; + } + + /* + * Grab the config mutex a little early incase update_session + * is running and something went wacko, the connect/login timer + * above will break us out. + */ + if (down_interruptible(&session->config_mutex)) { + iscsi_host_err(session, "Failed to acquire mutex before " + "login\n"); + goto done; + } + + /* + * initialize session fields for the iscsi-login code + */ + session->type = ISCSI_SESSION_TYPE_NORMAL; + /* + * use iSCSI default, unless declared otherwise by the + * target during login + */ + session->max_xmit_data_segment_len = + DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH; + session->vendor_specific_keys = 1; + /* + * we do not want to allocate memory here since this might be a + * relogin with IO in progress, so we reuse the rx_buffer. Note + * that extra care must be taken when using this buffer for both + * send and recv here , becuase the net subsys does not copy data + * in sendpage. + */ + login_status = iscsi_login(session, session->rx_buffer, + sizeof(session->rx_buffer), &status_class, + &status_detail); + up(&session->config_mutex); + + ret = login_response_status(session, login_status); + if (ret < 1) + goto done; + + ret = check_iscsi_status_class(session, status_class, status_detail); + if (ret < 2) + goto done; + + iscsi_host_notice(session, "Session established\n"); + /* + * logged in ok, get the new session ready + */ + session->window_closed = 0; + session->session_established_time = jiffies; + session->session_drop_time = 0; + clear_bit(SESSION_WINDOW_CLOSED, &session->control_bits); + spin_lock_bh(&session->task_lock); + clear_bit(SESSION_REPLACEMENT_TIMEDOUT, &session->control_bits); + set_bit(SESSION_ESTABLISHED, &session->control_bits); + spin_unlock_bh(&session->task_lock); + /* + * ready to go, so wake up everyone waiting for the session + * to be established + */ + wake_up(&session->login_wait_q); + done: + /* + * there is a race with the login timer here where we successfully + * login, but then the login timer expires. If this does occur + * we end up relogging in. To handle the login_wait_q + * being woken up we are holding the tx_blocked sema so the tx_thread + * will not be sending any tasks while this is going on (the worst + * that happens is tasks will timeout). + * + * Fixme: if time (this should be rare so maybe not a priority) + */ + spin_lock(&session->portal_lock); + clear_bit(SESSION_IN_LOGIN, &session->control_bits); + del_timer_sync(&session->login_timer); + spin_unlock(&session->portal_lock); + + /* cleanup after a possible timeout expiration */ + if (signal_pending(current)) { + flush_signals(current); + + if (test_bit(SESSION_TERMINATING, &session->control_bits)) + return 0; + else + return -1; + } + return ret; +} + +static char* +iscsi_strdup(char *str, int *err) +{ + int len; + char *s; + + *err = 0; + len = strlen(str) + 1; + if (len == 1) { + *err = -EINVAL; + return NULL; + } + + s = kmalloc(len, GFP_KERNEL); + if (!s) { + *err = -ENOMEM; + return NULL; + } + + return strcpy(s, str); +} + +/* + * return value: + * 1: name/alias updated. Relogin required. + * 0: No updated needed. + * -Exxx: Failed to update. + */ +static int +update_iscsi_strings(struct iscsi_session *session, + struct iscsi_session_ioctl *ioctld) +{ + char *iname = NULL; + char *alias = NULL; + char *uname = NULL; + char *uname_in = NULL; + char *pw = NULL; + char *pw_in = NULL; + int rc = 0; + + /* + * update all the values or none of them + */ + if (!ioctld->initiator_name[0]) { + iscsi_host_err(session, "No InitiatorName\n"); + return -EINVAL; + } + if (strcmp(ioctld->initiator_name, session->initiator_name)) { + iname = iscsi_strdup(ioctld->initiator_name, &rc); + if (!iname) { + iscsi_host_err(session, "Failed to change " + "InitiatorName from %s to %s\n", + session->initiator_name, + ioctld->initiator_name); + return rc; + } + } + + if (ioctld->initiator_alias[0] && (!session->initiator_alias || + strcmp(ioctld->initiator_alias, session->initiator_alias))) { + alias = iscsi_strdup(ioctld->initiator_alias, &rc); + if (!alias) + /* Alias is not ciritical so just print an error */ + iscsi_host_err(session, "Failed to change " + "InitiatorAlias\n"); + } + + if (ioctld->username[0] && (!session->username || + strcmp(ioctld->username, session->username))) { + uname = iscsi_strdup(ioctld->username, &rc); + if (!uname) { + iscsi_host_err(session, "Failed to change outgoing " + "username\n"); + goto failed; + } + } + + if (ioctld->username_in[0] && (!session->username_in || + strcmp(ioctld->username_in, session->username_in))) { + uname_in = iscsi_strdup(ioctld->username_in, &rc); + if (!uname_in) { + iscsi_host_err(session, "Failed to change incoming " + "username\n"); + goto failed; + } + } + + if (ioctld->password_length && (!session->password || + session->password_length != ioctld->password_length || + memcmp(ioctld->password, session->password, + session->password_length))) { + pw = kmalloc(ioctld->password_length + 1, GFP_KERNEL); + if (!pw) { + iscsi_host_err(session, "Failed to change outgoing " + "password\n"); + rc = -ENOMEM; + goto failed; + } + memcpy(pw, ioctld->password, ioctld->password_length); + } + + if (ioctld->password_length_in && (!session->password_in || + session->password_length_in != ioctld->password_length_in || + memcmp(ioctld->password_in, session->password_in, + session->password_length_in))) { + pw_in = kmalloc(ioctld->password_length_in + 1, GFP_KERNEL); + if (!pw_in) { + iscsi_host_err(session, "Failed to change incoming " + "password\n"); + rc = -ENOMEM; + goto failed; + } + memcpy(pw_in, ioctld->password_in, ioctld->password_length_in); + } + + if (iname) { + kfree(session->initiator_name); + session->initiator_name = iname; + rc = 1; + } + if (alias || (!ioctld->initiator_alias[0] && + session->initiator_alias[0])) { + kfree(session->initiator_alias); + session->initiator_alias = alias; + rc = 1; + } + if (uname || (!ioctld->username[0] && session->username)) { + kfree(session->username); + session->username = uname; + rc = 1; + } + if (uname_in || (!ioctld->username_in[0] && session->username_in)) { + kfree(session->username_in); + session->username_in = uname_in; + rc = 1; + } + if (pw || (!ioctld->password_length && session->password)) { + kfree(session->password); + session->password = pw; + session->password_length = ioctld->password_length; + rc = 1; + } + if (pw_in || (!ioctld->password_length_in && session->password_in)) { + kfree(session->password_in); + session->password_in = pw_in; + session->password_length_in = ioctld->password_length_in; + rc = 1; + } + return rc; + failed: + kfree(iname); + kfree(alias); + kfree(uname); + kfree(uname_in); + kfree(pw); + kfree(pw_in); + return rc; +} + +static int +alloc_auth_buffers(struct iscsi_session *session) +{ + if (!(session->bidirectional_auth || session->username || + session->password)) + return 0; + + if (session->auth_client_block) + return 0; + + session->md5_tfm = crypto_alloc_tfm("md5", 0); + if (!session->md5_tfm) + return -ENOMEM; + + session->auth_client_block = + kmalloc(sizeof(*session->auth_client_block), GFP_KERNEL); + if (!session->auth_client_block) + goto error; + + session->auth_recv_string_block = + kmalloc(sizeof(*session->auth_recv_string_block), GFP_KERNEL); + if (!session->auth_recv_string_block) + goto error; + + session->auth_send_string_block = + kmalloc(sizeof(*session->auth_send_string_block), GFP_KERNEL); + if (!session->auth_send_string_block) + goto error; + + session->auth_recv_binary_block = + kmalloc(sizeof(*session->auth_recv_binary_block), GFP_KERNEL); + if (!session->auth_recv_binary_block) + goto error; + + session->auth_send_binary_block = + kmalloc(sizeof(*session->auth_send_binary_block), GFP_KERNEL); + if (!session->auth_send_binary_block) + goto error; + + return 0; + + error: + crypto_free_tfm(session->md5_tfm); + kfree(session->auth_client_block); + kfree(session->auth_recv_string_block); + kfree(session->auth_send_string_block); + kfree(session->auth_recv_binary_block); + iscsi_host_err(session, "Session requires authentication but couldn't " + "allocate authentication stuctures\n"); + return -ENOMEM; +} + +void +iscsi_update_ping_timeout(struct iscsi_session *session, int timeout) +{ + if (timeout < 0) { + iscsi_host_err(session, "Cannot set negative timeout value of" + "%d\n", timeout); + return; + } + + spin_lock_bh(&session->task_lock); + if (timeout == session->ping_timeout) + goto done; + + /* reset these for the next timer */ + session->last_rx = jiffies; + session->last_ping = jiffies; + /* this will be used for the next ping */ + session->ping_timeout = timeout; + done: + spin_unlock_bh(&session->task_lock); +} + +void +iscsi_update_active_timeout(struct iscsi_session *session, int timeout) +{ + if (timeout < 0) { + iscsi_host_err(session, "Cannot set negative timeout value of" + "%d\n", timeout); + return; + } + + spin_lock_bh(&session->task_lock); + if (timeout == session->active_timeout) + goto done; + + if (!session->num_active_tasks) + goto done; + + /* reset these for the next timer */ + session->last_rx = jiffies; + session->last_ping = jiffies; + + if ((del_timer(&session->transport_timer) && timeout) || + (!session->active_timeout && timeout)) + mod_timer(&session->transport_timer, jiffies + (timeout * HZ)); + done: + session->active_timeout = timeout; + spin_unlock_bh(&session->task_lock); +} + +void +iscsi_update_idle_timeout(struct iscsi_session *session, int timeout) +{ + if (timeout < 0) { + iscsi_host_err(session, "Cannot set negative timeout value of" + "%d\n", timeout); + return; + } + + spin_lock_bh(&session->task_lock); + if (timeout == session->idle_timeout) + goto done; + + if (session->num_active_tasks) + goto done; + + /* reset these for the next timer */ + session->last_rx = jiffies; + session->last_ping = jiffies; + + if ((del_timer(&session->transport_timer) && timeout) || + (!session->idle_timeout && timeout)) + mod_timer(&session->transport_timer, jiffies + (timeout * HZ)); + done: + session->idle_timeout = timeout; + spin_unlock_bh(&session->task_lock); +} + +int +iscsi_update_session(struct iscsi_session *session, + struct iscsi_session_ioctl *ioctld) +{ + int rc = 0; + int relogin = 0; + + if (down_interruptible(&session->config_mutex)) { + iscsi_host_err(session, "Session configuration update aborted " + "by signal\n"); + return -EINTR; + } + if (test_bit(SESSION_TERMINATED, &session->control_bits)) + return -EINVAL; + + if (ioctld->update && (ioctld->config_number < session->config_number)) + /* this update is obsolete, ignore it */ + goto err_exit; + + if (ioctld->username_in[0] || ioctld->password_length_in) + session->bidirectional_auth = 1; + else + session->bidirectional_auth = 0; + rc = alloc_auth_buffers(session); + if (rc < 0) + goto err_exit; + + rc = update_iscsi_strings(session, ioctld); + if (rc > 0) + relogin = 1; + else if (rc < 0) + goto err_exit; + + session->config_number = ioctld->config_number; + + /* + * the portals are guarded by a spinlock instead of the config + * mutex, so that we can request portal changes while a login is + * occuring. + */ + spin_lock(&session->portal_lock); + if (iscsi_update_portal_info(&session->portal, &ioctld->portal)) + relogin = 1; + spin_unlock(&session->portal_lock); + + /* + * update timers + */ + iscsi_update_abort_timeout(session, ioctld->abort_timeout); + iscsi_update_reset_timeout(session, ioctld->reset_timeout); + iscsi_update_idle_timeout(session, ioctld->idle_timeout); + iscsi_update_active_timeout(session, ioctld->active_timeout); + iscsi_update_ping_timeout(session, ioctld->ping_timeout); + iscsi_update_replacement_timeout(session, ioctld->replacement_timeout); + iscsi_update_login_timeout(session, ioctld->login_timeout); + + if (relogin) { + spin_lock_bh(&session->task_lock); + iscsi_request_logout(session, 3, session->active_timeout); + spin_unlock_bh(&session->task_lock); + } + /* + * after we release the mutex we cannot touch any field that + * may be freed by a shutdown that is running at the same time + */ + up(&session->config_mutex); + + return 0; + + err_exit: + up(&session->config_mutex); + return rc; +} + +static int +copy_iscsi_strings(struct iscsi_session *session, + struct iscsi_session_ioctl *ioctld) +{ + int rc; + + session->initiator_name = iscsi_strdup(ioctld->initiator_name, &rc); + if (rc == -EINVAL) { + iscsi_host_err(session, "No InitiatorName\n"); + return rc; + } + if (rc == -ENOMEM) { + iscsi_host_err(session, "Cannot allocate InitiatorName\n"); + return rc; + } + + session->initiator_alias = iscsi_strdup(ioctld->initiator_alias, &rc); + /* Alias is not ciritical so just print an error */ + if (!session->initiator_alias) + iscsi_host_err(session, "Cannot create InitiatorAlias\n"); + + session->target_name = iscsi_strdup(ioctld->target_name, &rc); + if (rc == -EINVAL) { + iscsi_err("No TargetName\n"); + return rc; + } + if (rc == -ENOMEM) { + iscsi_host_err(session, "Cannot allocate TargetName\n"); + return rc; + } + + session->username = iscsi_strdup(ioctld->username, &rc); + if (rc == -ENOMEM) { + iscsi_host_err(session, "Failed to allocate outgoing " + "username\n"); + return rc; + } + + session->username_in = iscsi_strdup(ioctld->username_in, &rc); + if (rc == -ENOMEM) { + iscsi_host_err(session, "Failed to allocate incoming " + "username\n"); + return rc; + } + + if (ioctld->password_length) { + session->password = kmalloc(ioctld->password_length + 1, + GFP_KERNEL); + if (!session->password) { + iscsi_host_err(session, "Failed to allocate outgoing " + "password\n"); + return -ENOMEM; + } + memcpy(session->password, ioctld->password, + ioctld->password_length); + session->password_length = ioctld->password_length; + } + + if (ioctld->password_length_in) { + session->password_in = kmalloc(ioctld->password_length_in + 1, + GFP_KERNEL); + if (!session->password_in) { + iscsi_host_err(session, "Failed to allocate incoming " + "password\n"); + return -ENOMEM; + } + memcpy(session->password_in, ioctld->password_in, + ioctld->password_length_in); + session->password_length_in = ioctld->password_length_in; + } + + return 0; +} + +/** + * clear_session - clear session fields before attempting a re-login. + * @session: session to initialize. + **/ +static void +clear_session(struct iscsi_session *session) +{ + struct iscsi_nop_info *nop_info, *tmp; + + session->nop_reply.ttt = ISCSI_RSVD_TASK_TAG; + list_for_each_entry_safe(nop_info, tmp, &session->nop_reply_list, + reply_list) { + list_del(&nop_info->reply_list); + kfree(nop_info); + } + + spin_unlock_bh(&session->task_lock); + del_timer_sync(&session->transport_timer); + del_timer_sync(&session->logout_timer); + spin_lock_bh(&session->task_lock); + + clear_bit(SESSION_IN_LOGOUT, &session->control_bits); + clear_bit(SESSION_LOGOUT_REQUESTED, &session->control_bits); + session->logout_response_timeout = 0; + session->last_mgmt_itt = ISCSI_RSVD_TASK_TAG; +} + +/* + * Timer processing for a session in Full Feature Phase (minus logout). + * This timer may rearm itself. + */ +static void +check_transport_timeouts(unsigned long data) +{ + struct iscsi_session *session = (struct iscsi_session *)data; + unsigned long timeout, next_timeout = 0, last_rx; + + spin_lock(&session->task_lock); + + if (test_bit(SESSION_TERMINATED, &session->control_bits) || + !test_bit(SESSION_ESTABLISHED, &session->control_bits)) + goto done; + + if (session->num_active_tasks) + timeout = session->active_timeout; + else + timeout = session->idle_timeout; + if (!timeout) + goto check_window; + + timeout *= HZ; + last_rx = session->last_rx; + + if (session->ping_timeout && + time_before_eq(last_rx + timeout + (session->ping_timeout * HZ), + jiffies)) { + iscsi_host_err(session, "ping timeout of %d secs expired, " + "last rx %lu, last ping %lu, now %lu\n", + session->ping_timeout, last_rx, + session->last_ping, jiffies); + iscsi_drop_session(session); + goto done; + } + + if (time_before_eq(last_rx + timeout, jiffies)) { + if (time_before_eq(session->last_ping, last_rx)) { + /* + * send a ping to try to provoke some + * traffic + */ + session->last_ping = jiffies; + iscsi_wake_tx_thread(TX_PING, session); + } + next_timeout = last_rx + timeout + (session->ping_timeout * HZ); + } else + next_timeout = last_rx + timeout; + + check_window: + /* + * Do we still want to do this, or was it for an older + * bad target that has been fixed? + */ + if (test_bit(SESSION_WINDOW_CLOSED, &session->control_bits)) { + /* + * command window closed, ping once every 5 secs to ensure + * we find out when it re-opens. Target should send + * us an update when it does, but we're not very + * trusting of target correctness. + */ + if (time_before(session->last_ping + (5 * HZ), jiffies)) + iscsi_wake_tx_thread(TX_PING, session); + if (next_timeout) + next_timeout = min(jiffies + (5 * HZ), next_timeout); + else + next_timeout = jiffies + (5 * HZ); + } + + if (next_timeout) + mod_timer(&session->transport_timer, next_timeout); + done: + spin_unlock(&session->task_lock); +} + +static void +replacement_timed_out(unsigned long data) +{ + struct iscsi_session *session = (struct iscsi_session *)data; + + iscsi_host_err(session, "replacement session time out after %d " + "seconds, drop %lu, now %lu, failing all commands\n", + session->replacement_timeout, + session->session_drop_time, jiffies); + + spin_lock(&session->task_lock); + if (test_bit(SESSION_ESTABLISHED, &session->control_bits) || + test_and_set_bit(SESSION_REPLACEMENT_TIMEDOUT, + &session->control_bits)) { + spin_unlock(&session->task_lock); + return; + } + iscsi_flush_queues(session, ISCSI_MAX_LUNS, DID_BUS_BUSY); + spin_unlock(&session->task_lock); + + wake_up_all(&session->login_wait_q); +} + +static void +init_session_structure(struct iscsi_session *session, + struct iscsi_session_ioctl *ioctld) +{ + INIT_LIST_HEAD(&session->list); + session->config_number = ioctld->config_number; + spin_lock_init(&session->portal_lock); + session->portal_group_tag = -1; + /* the first down should block */ + sema_init(&session->config_mutex, 0); + INIT_LIST_HEAD(&session->pending_queue); + INIT_LIST_HEAD(&session->active_queue); + INIT_LIST_HEAD(&session->done_queue); + spin_lock_init(&session->task_lock); + INIT_LIST_HEAD(&(session->tx_task_head)); + init_waitqueue_head(&session->tx_wait_q); + init_waitqueue_head(&session->login_wait_q); + sema_init(&session->tx_blocked, 0); + session->next_itt = 1; + session->time2wait = -1; + session->last_mgmt_itt = ISCSI_RSVD_TASK_TAG; + session->mgmt_task_complete = NULL; + session->nop_reply.ttt = ISCSI_RSVD_TASK_TAG; + INIT_LIST_HEAD(&session->nop_reply_list); + + session->login_timeout = ioctld->login_timeout; + session->active_timeout = ioctld->active_timeout; + session->idle_timeout = ioctld->idle_timeout; + session->ping_timeout = ioctld->ping_timeout; + session->abort_timeout = ioctld->abort_timeout; + session->reset_timeout = ioctld->reset_timeout; + session->replacement_timeout = ioctld->replacement_timeout; + + init_timer(&session->transport_timer); + session->transport_timer.data = (unsigned long)session; + session->transport_timer.function = check_transport_timeouts; + + init_timer(&session->logout_timer); + session->logout_timer.data = (unsigned long)session; + session->logout_timer.function = handle_logout_timeouts; + + init_timer(&session->replacement_timer); + session->replacement_timer.data = (unsigned long)session; + session->replacement_timer.function = replacement_timed_out; + + init_timer(&session->login_timer); + session->login_timer.data = (unsigned long)session; + session->login_timer.function = login_timed_out; + + init_timer(&session->tmf_timer); + session->tmf_timer.function = iscsi_tmf_times_out; +} + +/** + * iscsi_mod_session_timer - modify the session's transport timer + * @session: iscsi session + * @timeout: timeout in seconds + * + * Note: + * Must hold the task lock. And, if the new timeout was shorter + * than the window_closed_timeout we will end up delaying the + * new timeout. This should be rare and not really hurt anything + * so we ignore it for now. + **/ +void +iscsi_mod_session_timer(struct iscsi_session *session, int timeout) +{ + /* + * reset last_rx and last_ping, so that it does not look like + * we timed out when we are just switching states + */ + session->last_rx = jiffies; + session->last_ping = jiffies; + + if (test_bit(SESSION_WINDOW_CLOSED, &session->control_bits)) + return; + + if (timeout) + mod_timer(&session->transport_timer, jiffies + (timeout * HZ)); + else + del_timer(&session->transport_timer); +} + +void +iscsi_wake_tx_thread(int control_bit, struct iscsi_session *session) +{ + set_bit(control_bit, &session->control_bits); + set_bit(TX_WAKE, &session->control_bits); + wake_up(&session->tx_wait_q); +} + +/** + * iscsi_wait_for_session - Wait for a session event to be established. + * @session: session to wait on. + * @ignore_timeout: If zero this will return when the replacement timeout fires. + * + * Description: + * Returns 1 to indicate sesssion was established, or 0 to indicate + * we timed out (if ignore_timeout == 0) or are terminating. + **/ +int +iscsi_wait_for_session(struct iscsi_session *session, int ignore_timeout) +{ + int rc = 0; + + while (1) { + wait_event_interruptible(session->login_wait_q, + test_bit(SESSION_ESTABLISHED, &session->control_bits) || + test_bit(SESSION_TERMINATING, &session->control_bits) || + (!ignore_timeout && + test_bit(SESSION_REPLACEMENT_TIMEDOUT, + &session->control_bits))); + + if (signal_pending(current)) + flush_signals(current); + + /* + * need to test for termnination first to avoid falling + * in the tx request loop for ever + */ + if (test_bit(SESSION_TERMINATING, &session->control_bits)) + break; + + if (test_bit(SESSION_ESTABLISHED, &session->control_bits)) { + rc = 1; + break; + } + + if (!ignore_timeout && test_bit(SESSION_REPLACEMENT_TIMEDOUT, + &session->control_bits)) + break; + } + + return rc; +} + +/* + * Note the ordering matches the TX_* bit ordering. + * See iscsi_tx_thread comment, this basically a + * workqueue_struct. + */ +static struct { + void (* request_fn)(struct iscsi_session *); +} tx_request_fns[] = { + { iscsi_send_nop_out }, + { iscsi_send_task_mgmt }, + { iscsi_run_pending_queue }, + { iscsi_send_nop_replys }, + { iscsi_send_r2t_data }, + { iscsi_send_logout }, +}; + +static void +wait_for_tx_requests(struct iscsi_session *session) +{ + int req; + + wait_event_interruptible(session->tx_wait_q, + test_and_clear_bit(TX_WAKE, &session->control_bits)); + + for (req = 0; req < TX_WAKE; req++) { + if (signal_pending(current)) + return; + /* + * when a logout is in progress or about to be sent + * we do not start new requests, but we continue to + * respond to R2Ts and Nops. + */ + if (test_and_clear_bit(req, &session->control_bits)) { + if (test_bit(SESSION_LOGOUT_REQUESTED, + &session->control_bits) && + req <= TX_SCSI_COMMAND) + continue; + + tx_request_fns[req].request_fn(session); + } + } +} + +/** + * session_kthread_sleep - put a thread to sleep while waiting for shutdown. + * @session: session. + * + * Description: + * If for some reason we could not relogin into a session we sleep here + * and and wait for someone to remove the session. Returns -EPERM to + * indicate the thread should exit, or zero to indicate that the thread + * can proceed with its normal action. + **/ +static inline int +session_kthread_sleep(struct iscsi_session *session) +{ + retest: + set_current_state(TASK_INTERRUPTIBLE); + if (kthread_should_stop()) { + __set_current_state(TASK_RUNNING); + return -EPERM; + } + + /* + * We fall into this sleep, when someone has broken us + * out of the lower loops that process requests or log us in, + * terminate the session (session drops will not sleep here), + * but have not (yet) cleaned up the host and called kthread_stop()). + */ + if (test_bit(SESSION_TERMINATING, &session->control_bits)) { + schedule(); + if (signal_pending(current)) + flush_signals(current); + goto retest; + } + __set_current_state(TASK_RUNNING); + return 0; +} + +/* + * the writer thread + * TODO? - this could be nicely replaced with a work queue + * having a work struct replacing each TX_* req, but will + * using a singlethreaded_workqueue hurt perf when all + * targets use the same cpu_workqueue_struct? + * Or to reduce the number of threads, should we use one + * per cpu workqueue for the entire driver for all sends? + */ +static int +iscsi_tx_thread(void *data) +{ + struct iscsi_session *session = data; + int rc; + unsigned long tmo; + + current->flags |= PF_MEMALLOC; + allow_signal(SIGHUP); + + /* + * tell the rx thread that we're about to block, and that + * it can safely call iscsi_sendmsg now as part of + * the Login phase. + */ + up(&session->tx_blocked); + + while (!session_kthread_sleep(session)) { + spin_lock(&session->portal_lock); + tmo = session->replacement_timeout * HZ; + if (tmo && session->session_drop_time) { + del_timer_sync(&session->replacement_timer); + mod_timer(&session->replacement_timer, jiffies + tmo); + } + spin_unlock(&session->portal_lock); + rc = iscsi_wait_for_session(session, 1); + spin_lock(&session->portal_lock); + del_timer_sync(&session->replacement_timer); + spin_unlock(&session->portal_lock); + if (!rc) + continue; + + down(&session->tx_blocked); + + /* + * make sure we start sending commands again, + * and clear any stale requests + */ + clear_bit(TX_TMF, &session->control_bits); + clear_bit(TX_LOGOUT, &session->control_bits); + clear_bit(TX_DATA, &session->control_bits); + set_bit(TX_PING, &session->control_bits); + set_bit(TX_SCSI_COMMAND, &session->control_bits); + set_bit(TX_WAKE, &session->control_bits); + + while (!signal_pending(current)) + wait_for_tx_requests(session); + flush_signals(current); + + up(&session->tx_blocked); + } + + return 0; +} + +static int +establish_session(struct iscsi_session *session, unsigned int login_delay) +{ + int rc; + unsigned long login_failures = 0; + + while (!test_bit(SESSION_ESTABLISHED, &session->control_bits)) { + if (login_delay) { + iscsi_host_notice(session, "Waiting %u seconds before " + "next login attempt\n", login_delay); + msleep_interruptible(login_delay * 1000); + } + + if (test_bit(SESSION_TERMINATING, &session->control_bits)) + return 0; + + rc = __establish_session(session); + if (rc > 0) + /* established or redirected */ + login_failures = 0; + else if (rc < 0) + /* failed, retry */ + login_failures++; + else { + /* failed, give up */ + iscsi_host_err(session, "Session giving up\n"); + set_bit(SESSION_TERMINATING, &session->control_bits); + return 0; + } + + /* slowly back off the frequency of login attempts */ + if (login_failures == 0) + login_delay = 0; + else if (login_failures < 30) + login_delay = 1; + else if (login_failures < 48) + login_delay = 5; + else if (!test_bit(SESSION_REPLACEMENT_TIMEDOUT, + &session->control_bits)) + login_delay = 10; + else + login_delay = 60; + } + + return 1; +} + +/** + * get_time2wait - return iSCSI DefaultTime2Wait + * @session: iscsi session + * @short_sessions: number of short sessions + * + * Description: + * Return DefaultTime2Wait. However, if the session dies really + * quicky after we reach FFP, we'll not be interoperable due to bugs + * in the target (or this driver) that send illegal opcodes, + * or disagreements about how to do CRC calculations. To + * avoid spinning, we track sessions with really short + * lifetimes, and decrease the login frequency if we keep + * getting session failures, like we do for login failures. + **/ +static unsigned int +get_time2wait(struct iscsi_session *session, unsigned long *short_sessions) +{ + unsigned int login_delay = 0; + + if (session->time2wait >= 0) { + login_delay = session->time2wait; + session->time2wait = -1; + } else + login_delay = session->def_time2wait; + + if (time_before_eq(session->session_drop_time, + session->session_established_time + (2 * HZ))) { + (*short_sessions)++; + + if (*short_sessions < 30) + login_delay = max_t(unsigned int, login_delay, 1); + else if (*short_sessions < 48) + login_delay = max_t(unsigned int, login_delay, 5); + else if (!test_bit(SESSION_REPLACEMENT_TIMEDOUT, + &session->control_bits)) + login_delay = max_t(unsigned int, login_delay, 10); + else + login_delay = max_t(unsigned int, login_delay, 60); + + iscsi_host_warn(session, "Session has ended quickly %lu times, " + "login delay %u seconds\n", *short_sessions, + login_delay); + } else + /* session lived long enough that the target is probably ok */ + *short_sessions = 0; + + return login_delay; +} + +static int +iscsi_rx_thread(void *data) +{ + struct iscsi_session *session = data; + struct iscsi_hdr hdr; + unsigned int login_delay = 0; + unsigned long short_sessions = 0; + + current->flags |= PF_MEMALLOC; + allow_signal(SIGHUP); + + down(&session->tx_blocked); + + while (!session_kthread_sleep(session)) { + if (!establish_session(session, login_delay)) + continue; + + spin_lock_bh(&session->task_lock); + iscsi_mod_session_timer(session, session->idle_timeout); + spin_unlock_bh(&session->task_lock); + up(&session->tx_blocked); + + while (!signal_pending(current)) + iscsi_recv_pdu(session, &hdr, session->header_digest, + session->rx_buffer, ISCSI_RXCTRL_SIZE, + session->data_digest); + flush_signals(current); + + login_delay = get_time2wait(session, &short_sessions); + /* + * if this is a session drop we need to wait for + * the tx thread to stop queueing and processing requests + * so we can resetup the socket. + */ + down(&session->tx_blocked); + + /* + * session dropped unexpectedly, often due to + * network problems + */ + iscsi_host_err(session, "Session dropped\n"); + spin_lock_bh(&session->task_lock); + iscsi_flush_queues(session, ISCSI_MAX_LUNS, DID_BUS_BUSY); + clear_session(session); + spin_unlock_bh(&session->task_lock); + } + + up(&session->tx_blocked); + /* + * If there are any commands left this will remove them. + */ + spin_lock_bh(&session->task_lock); + iscsi_flush_queues(session, ISCSI_MAX_LUNS, DID_NO_CONNECT); + spin_unlock_bh(&session->task_lock); + + return 0; +} + +static int +start_session_threads(struct iscsi_session *session) +{ + session->tx_task = kthread_run(iscsi_tx_thread, session, "iscsi-tx"); + if (IS_ERR(session->tx_task)) { + iscsi_host_err(session, "Failed to start tx thread, terminating" + " session\n"); + goto fail; + } + + session->rx_task = kthread_run(iscsi_rx_thread, session, "iscsi-rx"); + if (IS_ERR(session->rx_task)) { + iscsi_host_err(session, "Failed to start rx thread, terminating" + " session\n"); + goto shutdown_tx_thread; + } + + return 0; + + shutdown_tx_thread: + set_bit(SESSION_TERMINATING, &session->control_bits); + kthread_stop(session->tx_task); + fail: + return -EAGAIN; +} + +static void +free_session(struct iscsi_session *session) +{ + if (session->preallocated_task) + kmem_cache_free(iscsi_task_cache, session->preallocated_task); + + if (session->mgmt_task) + kmem_cache_free(iscsi_task_cache, session->mgmt_task); + + if (session->rx_tfm) + crypto_free_tfm(session->rx_tfm); + if (session->tx_tfm) + crypto_free_tfm(session->tx_tfm); + if (session->md5_tfm) + crypto_free_tfm(session->md5_tfm); + + kfree(session->auth_client_block); + kfree(session->auth_recv_string_block); + kfree(session->auth_send_string_block); + kfree(session->auth_recv_binary_block); + kfree(session->auth_send_binary_block); + kfree(session->username); + kfree(session->password); + kfree(session->username_in); + kfree(session->password_in); + kfree(session->initiator_name); + kfree(session->initiator_alias); + kfree(session->target_name); + kfree(session->target_alias); +} + +void +iscsi_destroy_session(struct iscsi_session *session) +{ + set_bit(SESSION_TERMINATING, &session->control_bits); + clear_bit(SESSION_ESTABLISHED, &session->control_bits); + + down(&iscsi_session_sem); + list_del(&session->list); + up(&iscsi_session_sem); + + session->session_drop_time = jiffies ? jiffies : 1; + signal_iscsi_threads(session); + + kthread_stop(session->tx_task); + kthread_stop(session->rx_task); + + iscsi_disconnect(session); + + set_bit(SESSION_TERMINATED, &session->control_bits); + + /* + * grab the config mutex to make sure update_session is not + * accessing the session fields we are going to free + */ + down(&session->config_mutex); + del_timer_sync(&session->transport_timer); + del_timer_sync(&session->logout_timer); + free_session(session); + up(&session->config_mutex); +} + +int +iscsi_create_session(struct iscsi_session *session, + struct iscsi_session_ioctl *ioctld) +{ + int rc; + + init_session_structure(session, ioctld); + + session->preallocated_task = kmem_cache_alloc(iscsi_task_cache, + GFP_KERNEL); + if (!session->preallocated_task) { + iscsi_host_err(session, "Couldn't preallocate task\n"); + rc = -ENOMEM; + goto free_session; + } + + session->mgmt_task = kmem_cache_alloc(iscsi_task_cache, GFP_KERNEL); + if (!session->mgmt_task) { + iscsi_host_err(session, "Couldn't preallocate mgmt task\n"); + rc = -ENOMEM; + goto free_session; + } + memset(session->mgmt_task, 0, sizeof(*session->mgmt_task)); + iscsi_init_task(session->mgmt_task); + + rc = copy_iscsi_strings(session, ioctld); + if (rc) + goto free_session; + + memcpy(session->isid, ioctld->isid, sizeof(session->isid)); + + /* + * FIXME: Do we have to check on both the username_in and + * password_length_in. Same with iscsi_update_session as well? Smitha + */ + if (ioctld->username_in[0] || ioctld->password_length_in) + session->bidirectional_auth = 1; + else + session->bidirectional_auth = 0; + rc = alloc_auth_buffers(session); + if (rc) + goto free_session; + + memcpy(&session->portal, &ioctld->portal, sizeof(ioctld->portal)); + iscsi_set_portal(session); + + /* + * preallocate rx/tx_tfm, so that we do not have to possibly + * call crypto_alloc_tfm (it uses GFP_KERNEL) while IO is queued. + */ + session->rx_tfm = crypto_alloc_tfm("crc32c", 0); + if (!session->rx_tfm) { + rc = -ENOMEM; + goto free_session; + } + + session->tx_tfm = crypto_alloc_tfm("crc32c", 0); + if (!session->tx_tfm) { + rc = -ENOMEM; + goto free_session; + } + + rc = start_session_threads(session); + up(&session->config_mutex); + if (rc) + goto free_session; + + down(&iscsi_session_sem); + list_add_tail(&session->list, &iscsi_sessions); + up(&iscsi_session_sem); + + wait_event_interruptible(session->login_wait_q, + test_bit(SESSION_ESTABLISHED, &session->control_bits)); + if (!test_bit(SESSION_ESTABLISHED, &session->control_bits)) { + iscsi_destroy_session(session); + return -ENOTCONN; + } + + return 0; + + free_session: + free_session(session); + return rc; +} + +struct iscsi_session * +iscsi_find_session(const char *target_name, u8 isid[6], int tpgt) +{ + struct iscsi_session *session; + + down(&iscsi_session_sem); + + list_for_each_entry(session, &iscsi_sessions, list) { + if (!strcmp(session->target_name, target_name) && + !memcmp(session->isid, isid, sizeof(session->isid)) && + session->portal_group_tag == tpgt) { + if (scsi_host_get(session->shost)) { + up(&iscsi_session_sem); + return session; + } + break; + } + } + + up(&iscsi_session_sem); + return NULL; +} + +int +iscsi_update_address(struct iscsi_session *session, char *address) +{ + struct sockaddr_in *addr; + char *tag; + char *port; + int err = 1; + + tag = strrchr(address, ','); + if (tag) { + *tag = '\0'; + tag++; + } + + port = strrchr(address, ':'); + if (port) { + *port = '\0'; + port++; + } + + /* + * Still only ipv4 is supported. No access to ipv6 + * to test so feel free to implement it later... + */ + if (address[0] == '[') { + iscsi_host_err(session, "Driver does not support ipv6 " + "addresses\n"); + err = 0; + goto done; + } + + addr = (struct sockaddr_in *)&session->addr; + addr->sin_addr.s_addr = in_aton(address); + if (port) + addr->sin_port = htons(simple_strtoul(port, NULL, 0)); + else + addr->sin_port = htons(ISCSI_TCP_PORT); + + done: + /* restore the original strings */ + if (tag) { + --tag; + *tag = ','; + } + + if (port) { + --port; + *port = ':'; + } + + return err; +} diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-session.h linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-session.h --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-session.h 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-session.h 2005-06-15 17:18:42.434206328 -0500 @@ -0,0 +1,264 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-session.h,v 1.1.2.34 2005/04/26 17:44:50 mikenc Exp $ + * + * define the iSCSI session structure needed by the login library + */ +#ifndef ISCSI_SESSION_H_ +#define ISCSI_SESSION_H_ + +#include +#include +#include +#include + +#include "iscsi-auth-client.h" +#include "iscsi-portal.h" + +struct iscsi_session_ioctl; +struct iscsi_task; + +/* used for replying to NOPs - kill me */ +struct iscsi_nop_info { + struct list_head reply_list; + u32 ttt; + unsigned char lun[8]; +}; + +#define ISCSI_RXCTRL_SIZE 4096 + +struct iscsi_session { + struct Scsi_Host *shost; + struct list_head list; + /* + * the config mutex along with the portal lock protect + * and serialize the creation and update of session info + */ + struct semaphore config_mutex; + u32 config_number; + /* + * iSCSI settings + */ + unsigned char *initiator_name; + unsigned char *initiator_alias; + unsigned char *target_name; + unsigned char *target_alias; + u8 isid[6]; + u16 tsih; + u32 cmd_sn; + u32 exp_cmd_sn; + u32 max_cmd_sn; + u32 exp_stat_sn; + int immediate_data; + int initial_r2t; + /* the value we declare */ + int max_recv_data_segment_len; + /* the value declared by the target */ + int max_xmit_data_segment_len; + int first_burst_len; + int max_burst_len; + int data_pdu_in_order; + int data_seq_in_order; + int def_time2wait; + int def_time2retain; + int header_digest; + int data_digest; + int type; + int current_stage; + int next_stage; + int partial_response; + int portal_group_tag; + int vendor_specific_keys; + int send_async_text; + unsigned int irrelevant_keys_bitmap; + u32 next_itt; + long time2wait; + /* + * Authentication settings + */ + char *username; + unsigned char *password; + int password_length; + char *username_in; + unsigned char *password_in; + int password_length_in; + struct crypto_tfm *md5_tfm; + int bidirectional_auth; + struct iscsi_acl *auth_client_block; + struct auth_str_block *auth_recv_string_block; + struct auth_str_block *auth_send_string_block; + struct auth_large_binary *auth_recv_binary_block; + struct auth_large_binary *auth_send_binary_block; + /* + * Portal/Network settings + * support ipv4 when we finish the interface + */ + struct socket *socket; + /* we only support ipv4 until we can find a setup to test */ + struct sockaddr addr; + int tcp_window_size; + /* + * The portal lock protects the portal and related fields + */ + spinlock_t portal_lock; + struct iscsi_portal_info portal; + /* + * various accounting sutff + */ + + /* + * *_time fields used to detect sessions that die as soo + * as we hit FF + */ + unsigned long session_drop_time; + unsigned long session_established_time; + /* + * timer fields + * + * The transport and tmf timers and timeouts are accessed + * under the task lock. + * + * The replacement timer and login timer and their timeouts + * are accessed under the portal lock. + */ + struct timer_list transport_timer; + struct timer_list logout_timer; + struct timer_list login_timer; + struct timer_list replacement_timer; + struct timer_list tmf_timer; + unsigned long last_rx; + unsigned long last_ping; + unsigned long window_closed; + int login_timeout; + int active_timeout; + int idle_timeout; + int ping_timeout; + int abort_timeout; + int reset_timeout; + int replacement_timeout; + int logout_response_timeout; + /* + * iSCSI task/request + * - Requests originating from SCSI-ml like scsi cmnds and + * management functions are task backed. + * - iSCSI requests like Nop, Logout or Login do not + * have a struct iscsi_task to avoid allocating memory + * when not needed. + * + * The task lock protects the task/cmnd queues and the + * access to the task when the tx and rx thread could + * be accessing it at the same time. + */ + spinlock_t task_lock; + struct iscsi_task *preallocated_task; + struct list_head pending_queue; + struct list_head active_queue; + struct list_head done_queue; + struct list_head tx_task_head; + int num_active_tasks; + struct iscsi_nop_info nop_reply; + struct list_head nop_reply_list; + /* itt of the last mgmt task we sent */ + u32 last_mgmt_itt; + /* preallocated task for TMFs */ + struct iscsi_task *mgmt_task; + struct completion *mgmt_task_complete; + /* + * thread control stuff + */ + unsigned long control_bits; + wait_queue_head_t tx_wait_q; + wait_queue_head_t login_wait_q; + struct semaphore tx_blocked; + struct task_struct *rx_task; + struct task_struct *tx_task; + struct crypto_tfm *rx_tfm; + struct crypto_tfm *tx_tfm; + /* + * preallocated buffer for iSCSI requests that have + * data, and do not originate from scsi-ml + */ + unsigned char rx_buffer[ISCSI_RXCTRL_SIZE]; +}; + +/* session control bits */ +enum { + /* + * the tx bits match the tx_request array in + * iscsi-initiator.c, so if you modify this don't forget + */ + TX_PING, /* NopOut, reply requested */ + TX_TMF, + TX_SCSI_COMMAND, + TX_NOP_REPLY, /* reply to a Nop-in from the target */ + TX_DATA, + TX_LOGOUT, + TX_WAKE, + + SESSION_CREATED, + SESSION_RELEASING, + /* + * must hold the task lock when accessing the + * SESSION_REPLACEMENT_TIMEDOUT and SESSION_ESTABLISHED bits + */ + SESSION_REPLACEMENT_TIMEDOUT, + SESSION_ESTABLISHED, + /* + * SESSION_IN_LOGIN is accessed under the portal_lock and is used for + * moding the login_timer. + */ + SESSION_IN_LOGIN, + SESSION_LOGOUT_REQUESTED, + SESSION_IN_LOGOUT, + SESSION_WINDOW_CLOSED, + SESSION_TERMINATING, + SESSION_TERMINATED, +}; + +extern void iscsi_wake_tx_thread(int control_bit, + struct iscsi_session *session); +extern void iscsi_request_logout(struct iscsi_session *session, int logout, + int logout_response); +extern void iscsi_drop_session(struct iscsi_session *session); +extern void iscsi_update_replacement_timeout(struct iscsi_session *session, + int timeout); +extern void iscsi_update_login_timeout(struct iscsi_session *session, + int timeout); +extern void iscsi_update_ping_timeout(struct iscsi_session *session, + int timeout); +extern void iscsi_update_active_timeout(struct iscsi_session *session, + int timeout); +extern void iscsi_update_idle_timeout(struct iscsi_session *session, + int timeout); +extern int iscsi_update_session(struct iscsi_session *session, + struct iscsi_session_ioctl *ioctld); +extern int iscsi_create_session(struct iscsi_session *session, + struct iscsi_session_ioctl *ioctld); +extern void iscsi_destroy_session(struct iscsi_session *session); +extern struct iscsi_session *iscsi_find_session(const char *target_name, + u8 isid[6], int tpgt); +extern int iscsi_update_address(struct iscsi_session *session, char *address); +extern int iscsi_wait_for_session(struct iscsi_session *session, + int ignore_timeout); +extern void iscsi_mod_session_timer(struct iscsi_session *session, int timeout); + +extern struct list_head iscsi_sessions; + +#endif diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-sfnet.h linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-sfnet.h --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-sfnet.h 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-sfnet.h 2005-06-15 17:23:13.951219409 -0500 @@ -0,0 +1,146 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-sfnet.h,v 1.3.2.8 2005/04/27 06:26:21 mikenc Exp $ + * + * Misc definitions for the iSCSI kernel module + */ +#ifndef ISCSI_SFNET_H_ +#define ISCSI_SFNET_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct iscsi_session; +struct iscsi_session_ioctl; +struct iscsi_task; +struct iscsi_hdr; + +#define ISCSI_DRIVER_VERSION "4:0.1.11-1" +#define ISCSI_MAX_CMD_LEN 16 +#define ISCSI_CMDS_PER_LUN 32 +#define ISCSI_MAX_CMDS_PER_LUN 128 +/* + * we rely on scsi-ml's starvation code here + */ +#define ISCSI_MAX_CAN_QUEUE 1024 +#define ISCSI_MAX_SG SG_ALL +#define ISCSI_MAX_SECTORS 1024 +#define ISCSI_MAX_LUNS 256 +#define ISCSI_MAX_TARGETS 1 +#define ISCSI_MAX_CHANNELS 0 + +#define ISCSI_PROC_NAME "iscsi-sfnet" + +#define iscsi_host_err(s, fmt, args...) \ + printk(KERN_ERR "iscsi-sfnet:host%d: "fmt, s->shost->host_no, ##args) +#define iscsi_err(fmt, args...) \ + printk(KERN_ERR "iscsi-sfnet: "fmt, ##args) + +#define iscsi_host_warn(s, fmt, args...) \ + printk(KERN_WARNING "iscsi-sfnet:host%d: "fmt, s->shost->host_no, \ + ##args) +#define iscsi_warn(fmt, args...) \ + printk(KERN_WARNING "iscsi-sfnet: "fmt, ##args) + +#define iscsi_host_notice(s, fmt, args...) \ + printk(KERN_NOTICE "iscsi-sfnet:host%d: "fmt, s->shost->host_no, ##args) +#define iscsi_notice(fmt, args...) \ + printk(KERN_NOTICE "iscsi-sfnet: "fmt, ##args) + +#define iscsi_host_info(s, fmt, args...) \ + printk(KERN_INFO "iscsi-sfnet:host%d: "fmt, s->shost->host_no, ##args) +#define iscsi_info(fmt, args...) \ + printk(KERN_INFO "iscsi-sfnet: "fmt, ##args) + +/* miscalleneous routines */ +extern unsigned int iscsi_command_attr(struct scsi_cmnd *sc); +extern void iscsi_complete_command(struct scsi_cmnd *sc); + +/* Routines related to Serial Number Arithmetic */ +extern int iscsi_sna_lt(u32 n1, u32 n2); +extern int iscsi_sna_lte(u32 n1, u32 n2); + +/* + * IO return values the driver uses in the send, recv + * and network code. + */ +enum { + ISCSI_IO_SUCCESS, + ISCSI_IO_ERR, + ISCSI_IO_CRC32C_ERR, + ISCSI_IO_INTR, + ISCSI_IO_INVALID_OP, +}; + +/* Routines to build and transmit iSCSI PDUs and/or data */ +extern void iscsi_send_scsi_cmnd(struct iscsi_task *task); +extern void iscsi_send_task_mgmt(struct iscsi_session *session); +extern void iscsi_send_r2t_data(struct iscsi_session *session); +extern void iscsi_send_nop_replys(struct iscsi_session *session); +extern void iscsi_send_logout(struct iscsi_session *session); +extern void iscsi_send_nop_out(struct iscsi_session *session); +extern void iscsi_queue_unsolicited_data(struct iscsi_task *task); +extern int iscsi_send_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr, + int hdr_digest, char *data, int data_digest); +extern int iscsi_recv_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr, + int hdr_digest, char *data, int data_len, + int data_digest); + +/* Routines to send and receive data on TCP/IP sockets */ +extern int iscsi_recvmsg(struct iscsi_session *session, struct kvec *iov, + size_t iovn, size_t size); +extern int iscsi_sendmsg(struct iscsi_session *session, struct kvec *iov, + size_t iovn, size_t size); +extern int iscsi_sendpage(struct iscsi_session *session, int flags, + struct page *pg, unsigned int pg_offset, + unsigned int len); +extern int iscsi_connect(struct iscsi_session *session); +extern void iscsi_disconnect(struct iscsi_session *session); + +/* Register a driver interface */ +extern int iscsi_register_interface(void); +extern void iscsi_unregister_interface(void); + +/* ioctl and sysfs uses these routines to interact with the initiator */ +extern int iscsi_destroy_host(struct Scsi_Host *shost); +extern int iscsi_create_host(struct iscsi_session_ioctl *ioctld); + +/* Global variables */ +extern struct class_device_attribute *iscsi_host_attrs[]; +extern struct device_attribute *iscsi_dev_attrs[]; +extern struct iscsi_function_template iscsi_fnt; + +static inline void sg_init_one(struct scatterlist *sg, + u8 *buf, unsigned int buflen) +{ + memset(sg, 0, sizeof(*sg)); + + sg->page = virt_to_page(buf); + sg->offset = offset_in_page(buf); + sg->length = buflen; +} + +#endif diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-task.c linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-task.c --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-task.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-task.c 2005-06-15 17:18:33.388471960 -0500 @@ -0,0 +1,720 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-task.c,v 1.1.2.29 2005/04/28 17:28:19 mikenc Exp $ + * + * Task creation, management and completion functions are defined here. + */ +#include +#include +#include +#include +#include + +#include "iscsi-protocol.h" +#include "iscsi-session.h" +#include "iscsi-task.h" +#include "iscsi-sfnet.h" + +void +iscsi_init_task(struct iscsi_task *task) +{ + task->flags = 0; + task->itt = ISCSI_RSVD_TASK_TAG; + task->ttt = ISCSI_RSVD_TASK_TAG; + task->rtt = ISCSI_RSVD_TASK_TAG; + INIT_LIST_HEAD(&task->queue); + INIT_LIST_HEAD(&task->task_group_link); + task->refcount = 1; + task->scsi_cmnd = NULL; +} + +/* caller must hold the session's task lock */ +struct iscsi_task * +iscsi_alloc_task(struct iscsi_session *session) +{ + struct iscsi_task *task; + + task = kmem_cache_alloc(iscsi_task_cache, GFP_ATOMIC); + if (!task) { + if (!session->preallocated_task) + return NULL; + + task = session->preallocated_task; + session->preallocated_task = NULL; + } + + memset(task, 0, sizeof(*task)); + iscsi_init_task(task); + task->session = session; + + return task; +} + +/** + * __iscsi_get_task - get a handle to a task + * @task: task to get a handle on + * + * Note: + * task_lock must be held when calling. + **/ +static inline void +__iscsi_get_task(struct iscsi_task *task) +{ + task->refcount++; +} + +/** + * __iscsi_put_task - release handle to a task + * @task: task to release a handle on + **/ +void +__iscsi_put_task(struct iscsi_task *task) +{ + struct scsi_cmnd *scmnd; + struct iscsi_session *session; + + if (--task->refcount) + return; + + BUG_ON(!list_empty(&task->task_group_link)); + + list_del(&task->queue); + scmnd = task->scsi_cmnd; + session = task->session; + + if (!session->preallocated_task) + session->preallocated_task = task; + else + kmem_cache_free(iscsi_task_cache, task); + + iscsi_complete_command(scmnd); +} + +/* + * Caller must hold task lock + */ +static inline void +queue_active_task(struct iscsi_task *task) +{ + struct iscsi_session *session = task->session; + + task->itt = iscsi_alloc_itt(session); + list_add_tail(&task->queue, &session->active_queue); + + if (session->num_active_tasks == 0) + iscsi_mod_session_timer(session, session->active_timeout); + session->num_active_tasks++; +} + +/** + * iscsi_complete_task - Complete a task + * @task: task to complete + * + * Note: + * This should only be used to complete pending commands + * or by iscsi_complete_task. See notes for iscsi_complete_task. + **/ +inline void +__iscsi_complete_task(struct iscsi_task *task) +{ + __set_bit(ISCSI_TASK_COMPLETED, &task->flags); + list_del_init(&task->queue); + list_add_tail(&task->queue, &task->session->done_queue); + /* + * release handle obtained from allocation in queuecommand + */ + __iscsi_put_task(task); +} + +/** + * iscsi_complete_task - Complete a task in the active queue. + * @task: task to complete + * + * Note: + * The caller must hold the task lock. This function does not actually + * complete the scsi command for the task. That is performed when all + * handles have been released. You should also have set the scsi cmnd + * status before calling this function. + **/ +void +iscsi_complete_task(struct iscsi_task *task) +{ + struct iscsi_session *session = task->session; + + if (list_empty(&task->queue)) { + iscsi_host_info(session, "task itt %u already removed from " + "active task queue\n", task->itt); + return; + } + + --session->num_active_tasks; + if (session->num_active_tasks == 0) { + iscsi_mod_session_timer(session, session->idle_timeout); + + if (test_bit(SESSION_LOGOUT_REQUESTED, &session->control_bits)) + iscsi_wake_tx_thread(TX_LOGOUT, session); + } + + if (session->mgmt_task_complete && + session->mgmt_task->rtt == task->itt) { + iscsi_host_info(session, "Completed task %u while abort " + "in progress. Waking scsi_eh thread.\n", + task->itt); + iscsi_complete_tmf_task(session->mgmt_task, + ISCSI_TASK_TMF_FAILED); + } + + __iscsi_complete_task(task); +} + +/** + * wait_for_task - wait for a task being accessed by the tx_thread to be freed + * @s: iscsi session + * @field: task field to test + * @val: value to test field for + * + * Note: + * This function only gets run by the eh, so performance is not + * critical. It is only used to wait when the tx thread is in + * the middle of transmitting a task and a TMF response is + * recieved for it at the same time. + * + * Caller must hold the task lock. Ignore drop signals becuase + * we want to wait for the tx thread to finish up first and + * release its ref to this task. + **/ +#define wait_for_task(s, field, val) \ +do { \ + struct iscsi_task *tsk; \ + \ + retry_##field: \ + list_for_each_entry(tsk, &s->done_queue, queue) \ + if (tsk->field == val) { \ + spin_unlock_bh(&s->task_lock); \ + ssleep(1); \ + spin_lock_bh(&s->task_lock); \ + goto retry_##field; \ + } \ +} while (0) + +/** + * iscsi_complete_tmf_task - Complete a task mgmt task. + * @task: task to complete + * @state: which task state bit to set. + * + * Note: + * The caller must hold the task lock. + **/ +void +iscsi_complete_tmf_task(struct iscsi_task *task, int state) +{ + struct iscsi_session *session = task->session; + struct iscsi_task *aborted_task; + struct completion *tmf_complete; + + if (list_empty(&task->queue)) + return; + list_del_init(&task->queue); + __set_bit(state, &task->flags); + tmf_complete = session->mgmt_task_complete; + session->mgmt_task_complete = NULL; + + --session->num_active_tasks; + if (session->num_active_tasks == 0) { + iscsi_mod_session_timer(session, session->idle_timeout); + + if (test_bit(SESSION_LOGOUT_REQUESTED, &session->control_bits)) + iscsi_wake_tx_thread(TX_LOGOUT, session); + } + + if (state != ISCSI_TASK_TMF_SUCCESS) + goto done; + + if (test_bit(ISCSI_TASK_ABORT, &task->flags)) { + /* + * if the abort failed becuase the task completed this is + * handled by the caller + */ + aborted_task = iscsi_find_session_task(session, task->rtt); + if (aborted_task) { + iscsi_host_info(session, "Cleaning up aborted task " + "itt %u\n", task->rtt); + /* + * abort succeeded, so cleanup that task here. + */ + if (!list_empty(&aborted_task->task_group_link)) { + list_del_init(&aborted_task->task_group_link); + __iscsi_put_task(aborted_task); + } + iscsi_complete_task(aborted_task); + __iscsi_put_task(aborted_task); + } + + wait_for_task(session, itt, task->rtt); + + } else if (test_bit(ISCSI_TASK_LU_RESET, &task->flags) || + test_bit(ISCSI_TASK_ABORT_TASK_SET, &task->flags)) { + iscsi_flush_queues(session, task->lun, DID_BUS_BUSY); + wait_for_task(session, lun, task->lun); + } else { + iscsi_flush_queues(session, ISCSI_MAX_LUNS, DID_BUS_BUSY); + wait_for_task(session, session, session); + } + done: + complete(tmf_complete); +} + +/* + * must hold the task lock + */ +u32 +iscsi_alloc_itt(struct iscsi_session *session) +{ + u32 itt = session->next_itt++; + /* iSCSI reserves 0xFFFFFFFF, this driver reserves 0 */ + if (session->next_itt == ISCSI_RSVD_TASK_TAG) + session->next_itt = 1; + return itt; +} + +/** + * iscsi_process_task_status - process the status and flag bits + * @task: iscsi task + * @sth: either a scsi respoonse or scsi data (with status flag set ) header + * + * Description: + * Perform status and flags processing, and handle common errors like + * digest errors or missing data. + **/ +void +iscsi_process_task_status(struct iscsi_task *task, struct iscsi_hdr *sth) +{ + struct iscsi_scsi_rsp_hdr *stsrh = (struct iscsi_scsi_rsp_hdr *)sth; + struct scsi_cmnd *sc = task->scsi_cmnd; + + sc->result = DID_OK << 16 | stsrh->cmd_status; + + if (test_bit(ISCSI_TASK_CRC_ERROR, &task->flags)) { + /* + * There was a digest error during data receive. + * Cause a command retry. + */ + if (sc->device->type == TYPE_TAPE) + sc->result = DID_PARITY << 16; + else + sc->result = DID_IMM_RETRY << 16; + sc->resid = sc->request_bufflen; + return; + } + + if (stsrh->flags & ISCSI_FLAG_DATA_UNDERFLOW) + sc->resid = ntohl(stsrh->residual_count); + else if (stsrh->flags & ISCSI_FLAG_DATA_OVERFLOW) { + /* + * Only report the error to scsi-ml for IO (do not report + * for sg and scsi-ml inserted commands) by using the underflow + * value to detect where it is coming from. This is what + * we should be doing for underflow, and is really not + * 100% correct for either since for scsi-ml commands + * underflow is not set and it does not check resid + * (and for overflow resid does not really matter anyways but + * this is to get the Cisco HW working with little headaches + * (we should have just done a blacklist if we are really + * breaking out the hacks in this version)) + */ + if (sc->underflow) + /* + * FIXME: not sure how to tell the SCSI layer + * of an overflow, so just give it an error + */ + sc->result = DID_ERROR << 16 | stsrh->cmd_status; + } else if (test_bit(ISCSI_TASK_READ, &task->flags) && + task->rxdata != sc->request_bufflen) + /* + * All the read data did not arrive. we don't know + * which parts of the buffer didn't get data, so + * report the whole buffer missing + */ + sc->resid = sc->request_bufflen; +} + +void +iscsi_process_task_response(struct iscsi_task *task, + struct iscsi_scsi_rsp_hdr *stsrh, + unsigned char *sense_data, unsigned int sense_len) +{ + struct scsi_cmnd *sc = task->scsi_cmnd; + + iscsi_process_task_status(task, (struct iscsi_hdr *)stsrh); + /* + * If the target bothered to send sense (even without a check + * condition), we pass it along, since it may indicate a problem, + * and it's safer to report a possible problem than it is to assume + * everything is fine. + */ + if (sense_len) { + memset(sc->sense_buffer, 0, sizeof(sc->sense_buffer)); + memcpy(sc->sense_buffer, sense_data, + min((size_t)sense_len, sizeof(sc->sense_buffer))); + } +} + +void +iscsi_tmf_times_out(unsigned long data) +{ + struct iscsi_task *task = (struct iscsi_task *)data; + struct iscsi_session *session = task->session; + + spin_lock(&session->task_lock); + iscsi_host_err(session, "itt %u timed out\n", task->itt); + iscsi_complete_tmf_task(task, ISCSI_TASK_TMF_FAILED); + spin_unlock(&session->task_lock); +} + +/* + * for iscsi_update_*_timeout we rely on the eh thread + * not waking (and deleting the tmf timer) until a outstanding + * mgmt task is removed the session's active queue (iscsi_find_session_task + * == NULL) so that we do not need to hold a lock around the timer + * update. + */ +void +iscsi_update_abort_timeout(struct iscsi_session *session, int timeout) +{ + struct iscsi_task *task; + + if (timeout < 0) { + iscsi_host_err(session, "Cannot set negative timeout value of" + "%d\n", timeout); + return; + } + + spin_lock_bh(&session->task_lock); + if (timeout == session->abort_timeout) + goto done; + + task = iscsi_find_session_task(session, session->last_mgmt_itt); + if (!task) + goto done; + + if ((!test_bit(ISCSI_TASK_ABORT, &task->flags) && + !test_bit(ISCSI_TASK_ABORT_TASK_SET, &task->flags))) + goto done; + + if ((del_timer(&session->tmf_timer) && timeout) || + (!session->abort_timeout && timeout)) + mod_timer(&session->tmf_timer, jiffies + (timeout * HZ)); + done: + session->abort_timeout = timeout; + spin_unlock_bh(&session->task_lock); +} + +void +iscsi_update_reset_timeout(struct iscsi_session *session, int timeout) +{ + struct iscsi_task *task; + + if (timeout < 0) { + iscsi_host_err(session, "Cannot set negative timeout value of" + "%d\n", timeout); + return; + } + + spin_lock_bh(&session->task_lock); + if (timeout == session->reset_timeout) + goto done; + + task = iscsi_find_session_task(session, session->last_mgmt_itt); + if (!task) + goto done; + + if ((!test_bit(ISCSI_TASK_LU_RESET, &task->flags) && + !test_bit(ISCSI_TASK_TGT_WARM_RESET, &task->flags))) + goto done; + + if ((del_timer(&session->tmf_timer) && timeout) || + (!session->reset_timeout && timeout)) + mod_timer(&session->tmf_timer, jiffies + (timeout * HZ)); + done: + session->reset_timeout = timeout; + spin_unlock_bh(&session->task_lock); +} + +int +iscsi_exec_task_mgmt(struct iscsi_task *task, unsigned long timeout) +{ + struct iscsi_session *session = task->session; + DECLARE_COMPLETION(complete); + unsigned int reject_retry = 40; + + /* + * Did the last task mgmt fn timeout? + */ + if (session->last_mgmt_itt != ISCSI_RSVD_TASK_TAG) { + iscsi_host_info(session, "Outstanding task mgmt function %u " + "exists.\n", session->last_mgmt_itt); + return -1; + } + retry: + /* + * set this incase of timer updates that start a timer + */ + session->tmf_timer.data = (unsigned long)task; + if (timeout) + mod_timer(&session->tmf_timer, jiffies + (timeout * HZ)); + session->mgmt_task_complete = &complete; + + queue_active_task(task); + session->last_mgmt_itt = task->itt; + spin_unlock_bh(&session->task_lock); + + iscsi_host_info(session, "Waking tx_thread to send task mgmt " + "function itt %u\n", task->itt); + iscsi_wake_tx_thread(TX_TMF, session); + wait_for_completion(&complete); + del_timer_sync(&session->tmf_timer); + + spin_lock_bh(&session->task_lock); + + session->mgmt_task_complete = NULL; + /* + * we do not retry aborts on immediate rejects here, instead + * the caller should redrive it + */ + if (!test_bit(ISCSI_TASK_ABORT, &task->flags) && + __test_and_clear_bit(ISCSI_TASK_IMM_REJECT, &task->flags)) { + iscsi_host_err(session, "itt %u recieved immediate " + "reject. Sleeping for %u ms before retry\n", + task->itt, reject_retry); + + if (reject_retry <= 1280) { + spin_unlock_bh(&session->task_lock); + msleep_interruptible(reject_retry); + spin_lock_bh(&session->task_lock); + + reject_retry *= 2; + goto retry; + } + } + + return test_bit(ISCSI_TASK_TMF_SUCCESS, &task->flags) ? 0 : -1; +} + +static void +iscsi_set_direction(struct iscsi_task *task) +{ + switch (task->scsi_cmnd->sc_data_direction) { + case DMA_FROM_DEVICE: + __set_bit(ISCSI_TASK_READ, &task->flags); + break; + case DMA_TO_DEVICE: + __set_bit(ISCSI_TASK_WRITE, &task->flags); + break; + case DMA_BIDIRECTIONAL: + /* We do not yet support this */ + case DMA_NONE: + break; + } +} + +/** + * iscsi_run_pending_queue - process pending tasks. + * @session: the session to process. + * + * Note: + * Caller must not hold the task lock. + **/ +void +iscsi_run_pending_queue(struct iscsi_session *session) +{ + struct iscsi_task *task; + + spin_lock_bh(&session->task_lock); + + while (!signal_pending(current)) { + + if (!iscsi_sna_lte(session->cmd_sn, session->max_cmd_sn)) + break; + + if (test_bit(SESSION_LOGOUT_REQUESTED, &session->control_bits)) + break; + + if (list_empty(&session->pending_queue)) + break; + + task = list_entry(session->pending_queue.next, + struct iscsi_task, queue); + list_del_init(&task->queue); + + iscsi_set_direction(task); + queue_active_task(task); + + __iscsi_get_task(task); + iscsi_queue_unsolicited_data(task); + spin_unlock_bh(&session->task_lock); + /* + * we don't bother to check if the xmit works, since if it + * fails, the session will drop, and all tasks and cmnds + * will be completed by the drop. + */ + iscsi_send_scsi_cmnd(task); + spin_lock_bh(&session->task_lock); + __iscsi_put_task(task); + } + + spin_unlock_bh(&session->task_lock); +} + +static void +fail_task(struct iscsi_task *task, int result) +{ + struct scsi_cmnd *sc = task->scsi_cmnd; + + sc->resid = sc->request_bufflen; + sc->result = result << 16; + sc->sense_buffer[0] = 0x70; + sc->sense_buffer[2] = NOT_READY; + sc->sense_buffer[7] = 0x0; + + iscsi_host_err(task->session, "Failing command cdb 0x%02x task %u " + "with return code = 0x%x\n", sc->cmnd[0], task->itt, + sc->result); + /* + * was it pending + */ + if (task->itt == ISCSI_RSVD_TASK_TAG) + __iscsi_complete_task(task); + else { + if (!list_empty(&task->task_group_link)) { + list_del_init(&task->task_group_link); + __iscsi_put_task(task); + } + iscsi_complete_task(task); + } +} + +/** + * iscsi_flush_queues - Flush the active and pending queues. + * @session: session to search tasks for + * @lun: if lun is a valid value then only work on tasks on that lun + * if lun is greater than or equal to ISCSI_MAX_LUNS then work on all tasks + * @result: this should be a scsi-ml host_byte value + * + * Note: + * Caller must hold the task lock. + * The driver uses DID_BUS_BUSY to inidcate that it may be worth it + * to retry the command, but scsi-ml should have the final say (for + * tape, failfast, etc). And it uses DID_NO_CONNECT to indicate + * the session is gone and according to the replacment timeout not + * coming back so there is no point in retyring + **/ +void +iscsi_flush_queues(struct iscsi_session *session, unsigned int lun, int result) +{ + struct iscsi_task *task, *tmp; + + /* + * failing a task that is being aborted will lead to + * the TMF task being removed too, or completing a tmf could + * result in multiple tasks being removed. The task lock can also + * be dropped by iscsi_complete_tmf_task. + */ + restart: + list_for_each_entry_safe(task, tmp, &session->active_queue, queue) { + + if (lun < ISCSI_MAX_LUNS && task->lun != lun) + continue; + + if (task->scsi_cmnd) + fail_task(task, result); + else + /* + * This should only occur during session drops or + * session replacement timeouts. We report success + * since we are not going to get a response and all + * the cmnds are going to be returned back to scsi-ml. + */ + iscsi_complete_tmf_task(task, ISCSI_TASK_TMF_SUCCESS); + + goto restart; + } + + list_for_each_entry_safe(task, tmp, &session->pending_queue, queue) { + + if (lun < ISCSI_MAX_LUNS && task->lun != lun) + continue; + /* + * These commands have not even been sent, so there is + * no requirement to fail the command, but for a requeue + * there is no way to tell that the incoming commands + * were meant to be placed before the pending head or tail. + */ + fail_task(task, result); + } +} + +/* + * must hold the task_lock to call this + * TODO: if we cannot use the block layer tags we + * should use a non-linear algorithm. + */ +struct iscsi_task * +iscsi_find_session_task(struct iscsi_session *session, u32 itt) +{ + struct iscsi_task *task = NULL; + + list_for_each_entry(task, &session->active_queue, queue) + if (task->itt == itt) { + __iscsi_get_task(task); + return task; + } + return NULL; +} + +/* + * must hold the task_lock when calling this, and must release the + * handle acquired when adding the task to the collection + */ +inline struct iscsi_task * +iscsi_dequeue_r2t(struct iscsi_session *session) +{ + struct list_head *p; + + if (!list_empty(&session->tx_task_head)) { + p = session->tx_task_head.next; + list_del_init(p); + return list_entry(p, struct iscsi_task, task_group_link); + } + return NULL; +} + +/* + * Add a task to the collection. Must hold the task_lock to do this. + * This acquires a handle to the task that must be released when + * the task is dequeued and that caller is done using it + */ +inline void +iscsi_queue_r2t(struct iscsi_session *session, struct iscsi_task *task) +{ + if (list_empty(&task->task_group_link)) { + __iscsi_get_task(task); + list_add_tail(&task->task_group_link, &session->tx_task_head); + } +} diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-task.h linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-task.h --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-task.h 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-task.h 2005-06-15 17:18:42.434206328 -0500 @@ -0,0 +1,110 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-task.h,v 1.1.2.12 2005/04/26 17:44:50 mikenc Exp $ + * + * define the iSCSI task structure needed by the kernel module + */ +#ifndef ISCSI_TASK_H_ +#define ISCSI_TASK_H_ + +#include + +struct iscsi_session; +struct iscsi_hdr; +struct iscsi_scsi_rsp_hdr; + +/* task flags */ +enum { + /* + * ops + */ + ISCSI_TASK_WRITE, + ISCSI_TASK_READ, + ISCSI_TASK_ABORT, + ISCSI_TASK_ABORT_TASK_SET, + ISCSI_TASK_LU_RESET, + ISCSI_TASK_TGT_WARM_RESET, + /* + * internal driver state for the task + */ + ISCSI_TASK_INITIAL_R2T, + ISCSI_TASK_COMPLETED, + ISCSI_TASK_CRC_ERROR, + ISCSI_TASK_TMF_SUCCESS, + ISCSI_TASK_TMF_FAILED, + ISCSI_TASK_IMM_REJECT, +}; + +/* + * you must either have the task lock to access these fileds + * or be assured that the tx and rx thread are not going + * to able to access the filed at the same time. + */ +struct iscsi_task { + struct list_head queue; + struct list_head task_group_link; + struct scsi_cmnd *scsi_cmnd; + struct iscsi_session *session; + int refcount; + u32 rxdata; + unsigned long flags; + /* + * need to record so that aborts + * can set RefCmdSN properly + */ + u32 cmdsn; + u32 itt; + u32 ttt; + u32 rtt; + unsigned int data_offset; /* explicit R2T */ + int data_length; /* explicit R2T */ + unsigned int lun; +}; + +extern kmem_cache_t *iscsi_task_cache; +extern struct iscsi_task *iscsi_find_session_task(struct iscsi_session *session, + u32 itt); +extern struct iscsi_task *iscsi_alloc_task(struct iscsi_session *session); +extern void iscsi_init_task(struct iscsi_task *task); +extern void __iscsi_put_task(struct iscsi_task *task); +extern u32 iscsi_alloc_itt(struct iscsi_session *session); +extern struct iscsi_task *iscsi_dequeue_r2t(struct iscsi_session *session); +extern void iscsi_queue_r2t(struct iscsi_session *session, + struct iscsi_task *task); +extern void iscsi_process_task_response(struct iscsi_task *task, + struct iscsi_scsi_rsp_hdr *stsrh, + unsigned char *sense_data, + unsigned int senselen); +extern void iscsi_process_task_status(struct iscsi_task *task, + struct iscsi_hdr *sth); +extern void iscsi_run_pending_queue(struct iscsi_session *session); +extern void iscsi_flush_queues(struct iscsi_session *session, unsigned int lun, + int requeue); +extern void iscsi_complete_task(struct iscsi_task *task); +extern void __iscsi_complete_task(struct iscsi_task *task); +extern void iscsi_complete_tmf_task(struct iscsi_task *task, int state); +extern int iscsi_exec_task_mgmt(struct iscsi_task *task, unsigned long tmo); +extern void iscsi_update_abort_timeout(struct iscsi_session *session, + int timeout); +extern void iscsi_update_reset_timeout(struct iscsi_session *session, + int timeout); +extern void iscsi_tmf_times_out(unsigned long data); + +#endif diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-xmit-pdu.c linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-xmit-pdu.c --- linux-2.6.9/drivers/scsi/iscsi_sfnet/iscsi-xmit-pdu.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/iscsi-xmit-pdu.c 2005-06-15 17:18:33.388471960 -0500 @@ -0,0 +1,741 @@ +/* + * iSCSI driver for Linux + * Copyright (C) 2001 Cisco Systems, Inc. + * Copyright (C) 2004 Mike Christie + * Copyright (C) 2004 IBM Corporation + * maintained by linux-iscsi-devel@lists.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * See the file COPYING included with this distribution for more details. + * + * $Id: iscsi-xmit-pdu.c,v 1.1.2.28 2005/04/26 17:44:50 mikenc Exp $ + * + * Contains functions to handle transmission of iSCSI PDUs + */ +#include +#include +#include +#include +#include + +#include "iscsi-session.h" +#include "iscsi-task.h" +#include "iscsi-protocol.h" +#include "iscsi-login.h" +#include "iscsi-sfnet.h" + +static int +iscsi_send_header(struct iscsi_session *session, struct iscsi_hdr *hdr, + int hdr_digest) +{ + struct scatterlist sg; + struct kvec iov[2]; + u32 crc32c; + int len, iovn = 0; + + iov[iovn].iov_base = hdr; + iov[iovn].iov_len = sizeof(*hdr); + len = iov[iovn].iov_len; + iovn++; + + if (hdr_digest == ISCSI_DIGEST_CRC32C) { + crypto_digest_init(session->tx_tfm); + sg_init_one(&sg, (u8 *)hdr, len); + crypto_digest_digest(session->tx_tfm, &sg, 1, (u8*)&crc32c); + iov[iovn].iov_base = &crc32c; + iov[iovn].iov_len = sizeof(crc32c); + len += iov[iovn].iov_len; + iovn++; + } + + return iscsi_sendmsg(session, iov, iovn, len); +} + +static int +send_extra_data(struct iscsi_session *session, u32 data_len, int digest_opt) +{ + struct scatterlist sg; + struct kvec iov[2]; + int pad, iovn = 0, len = 0; + char padding[PAD_WORD_LEN - 1]; + u32 data_crc32c; + + if (data_len % PAD_WORD_LEN) { + pad = PAD_WORD_LEN - (data_len % PAD_WORD_LEN); + memset(padding, 0, pad); + iov[iovn].iov_base = padding; + iov[iovn].iov_len = pad; + iovn++; + len += pad; + + if (digest_opt == ISCSI_DIGEST_CRC32C) { + sg_init_one(&sg, padding, pad); + crypto_digest_update(session->tx_tfm, &sg, 1); + } + } + + if (data_len && digest_opt == ISCSI_DIGEST_CRC32C) { + crypto_digest_final(session->tx_tfm, (u8*)&data_crc32c); + iov[iovn].iov_base = &data_crc32c; + iov[iovn].iov_len = sizeof(data_crc32c); + len += iov[iovn].iov_len; + iovn++; + } + + if (iov) + return iscsi_sendmsg(session, iov, iovn, len); + else + return ISCSI_IO_SUCCESS; +} + +/** + * iscsi_send_sg_data - send SCSI data + * @session: iscsi session + * @sglist: scatterlist + * @start_sg: index into sglist to start from + * @sg_offset: offset in scatterlist entry to start from + * @sglist_len: number of entries in sglist + * @data_len: transfer length + * @digest_opt: CRC32C or NONE + * + * Note: + * iscsi_send_sg_data will set start_sg and sg_offset to the + * next starting values for future transfers from this scatterlist + * (if one is possible), for the caller. + **/ +static int +iscsi_send_sg_data(struct iscsi_session *session, struct scatterlist *sglist, + int *start_sg, u32 *sg_offset, int sglist_len, + u32 data_len, int digest_opt) +{ + unsigned int len, sg_bytes, pg_offset, remaining = data_len; + struct scatterlist tmpsg, *sg; + struct page *pg; + int i, rc, flags = MSG_MORE; + + if (digest_opt == ISCSI_DIGEST_CRC32C) + crypto_digest_init(session->tx_tfm); + /* + * loop over the scatterlist + */ + for (i = *start_sg; remaining > 0 && i < sglist_len; i++) { + sg = &sglist[i]; + + if (signal_pending(current)) + return ISCSI_IO_INTR; + + pg_offset = sg->offset + *sg_offset; + pg = sg->page + (pg_offset >> PAGE_SHIFT); + pg_offset -= (pg_offset & PAGE_MASK); + + /* + * set the offset and sg for the next pdu or loop + * iteration + */ + sg_bytes = sg->length - *sg_offset; + if (sg_bytes <= remaining) { + (*start_sg)++; + *sg_offset = 0; + } else { + *sg_offset = *sg_offset + remaining; + sg_bytes = remaining; + } + remaining -= sg_bytes; + + /* + * loop over each page in sg entry + */ + for (; sg_bytes > 0; sg_bytes -= len) { + len = min_t(unsigned int, sg_bytes, + PAGE_SIZE - pg_offset); + if (len == sg_bytes) + flags = 0; + + rc = iscsi_sendpage(session, flags, pg, pg_offset, len); + if (rc != ISCSI_IO_SUCCESS) + return rc; + + if (digest_opt == ISCSI_DIGEST_CRC32C) { + tmpsg.page = pg; + tmpsg.offset = pg_offset; + tmpsg.length = len; + crypto_digest_update(session->tx_tfm, + &tmpsg, 1); + } + + pg++; + pg_offset = 0; + } + } + + /* + * this should only happen for driver or scsi/block layer bugs + */ + if (remaining != 0) { + iscsi_host_err(session, "iscsi_send_sg_data - invalid sg list " + "start_sg %d, sg_offset %u, sglist_len %d " + "data_len %u, remaining %u\n", *start_sg, + *sg_offset, sglist_len, data_len, remaining); + return ISCSI_IO_INVALID_OP; + } + + return send_extra_data(session, data_len, digest_opt); +} + +int +iscsi_send_pdu(struct iscsi_session *session, struct iscsi_hdr *hdr, + int hdr_digest, char *data, int data_digest) +{ + struct scatterlist sg; + u32 data_len, offset = 0; + int rc, index = 0; + + rc = iscsi_send_header(session, hdr, hdr_digest); + if (rc != ISCSI_IO_SUCCESS) { + iscsi_drop_session(session); + goto done; + } + + data_len= ntoh24(hdr->dlength); + if (data && data_len) { + sg_init_one(&sg, data, data_len); + rc = iscsi_send_sg_data(session, &sg, &index, &offset, 1, + data_len, data_digest); + if (rc != ISCSI_IO_SUCCESS) + iscsi_drop_session(session); + } + + done: + return rc == ISCSI_IO_SUCCESS ? 1 : 0; +} + +static void +set_task_mgmt_attrs(struct iscsi_scsi_task_mgmt_hdr *ststmh, + struct iscsi_task *task) +{ + u8 tmf_code; + + if (test_bit(ISCSI_TASK_ABORT, &task->flags)) { + /* + * we reused cmdsn for refcmdsn for abort tasks. + */ + ststmh->refcmdsn = htonl(task->cmdsn); + ststmh->rtt = htonl(task->rtt); + ststmh->lun[1] = task->lun; + tmf_code = ISCSI_TMF_ABORT_TASK; + } else if (test_bit(ISCSI_TASK_ABORT_TASK_SET, &task->flags)) { + ststmh->lun[1] = task->lun; + tmf_code = ISCSI_TMF_ABORT_TASK_SET; + } else if (test_bit(ISCSI_TASK_LU_RESET, &task->flags)) { + ststmh->lun[1] = task->lun; + tmf_code = ISCSI_TMF_LOGICAL_UNIT_RESET; + } else + tmf_code = ISCSI_TMF_TARGET_WARM_RESET; + + ststmh->flags = ISCSI_FLAG_FINAL | (tmf_code & ISCSI_FLAG_TMF_MASK); +} + +void +iscsi_send_task_mgmt(struct iscsi_session *session) +{ + struct iscsi_task *task; + struct iscsi_scsi_task_mgmt_hdr ststmh; + int rc; + + spin_lock_bh(&session->task_lock); + + task = iscsi_find_session_task(session, session->last_mgmt_itt); + if (!task) { + /* + * timed out or session dropping + */ + spin_unlock_bh(&session->task_lock); + return; + } + + memset(&ststmh, 0, sizeof(struct iscsi_scsi_task_mgmt_hdr)); + ststmh.opcode = ISCSI_OP_TASK_MGT_REQ | ISCSI_OP_IMMEDIATE; + ststmh.rtt = ISCSI_RSVD_TASK_TAG; + ststmh.itt = htonl(task->itt); + ststmh.cmdsn = htonl(session->cmd_sn); + /* CmdSN not incremented after imm cmd */ + ststmh.expstatsn = htonl(session->exp_stat_sn); + set_task_mgmt_attrs(&ststmh, task); + + __iscsi_put_task(task); + spin_unlock_bh(&session->task_lock); + + rc = iscsi_send_header(session, (struct iscsi_hdr *)&ststmh, + session->header_digest); + if (rc != ISCSI_IO_SUCCESS) { + /* TODO drop session here still? */ + iscsi_host_err(session, "xmit_task_mgmt failed\n"); + iscsi_drop_session(session); + } +} + +/** + * iscsi_send_nop_out - transmit iscsi NOP-out + * @session: iscsi session + * @itt: Initiator Task Tag (must be in network byte order) + * @ttt: Target Transfer Tag (must be in network byte order) + * @lun: when ttt is valid, lun must be set + **/ +static void +__iscsi_send_nop_out(struct iscsi_session *session, u32 itt, u32 ttt, u8 *lun) +{ + struct iscsi_nop_out_hdr stph; + int rc; + + memset(&stph, 0, sizeof(stph)); + stph.opcode = ISCSI_OP_NOOP_OUT | ISCSI_OP_IMMEDIATE; + stph.flags = ISCSI_FLAG_FINAL; + stph.cmdsn = htonl(session->cmd_sn); + stph.expstatsn = htonl(session->exp_stat_sn); + if (lun) + memcpy(stph.lun, lun, sizeof(stph.lun)); + stph.ttt = ttt; + stph.itt = itt; + + rc = iscsi_send_header(session, (struct iscsi_hdr *)&stph, + session->header_digest); + if (rc != ISCSI_IO_SUCCESS) { + iscsi_host_err(session, "xmit_ping failed\n"); + /* mv drop ? */ + iscsi_drop_session(session); + } +} + +void +iscsi_send_nop_out(struct iscsi_session *session) +{ + u32 itt; + + spin_lock_bh(&session->task_lock); + itt = iscsi_alloc_itt(session); + spin_unlock_bh(&session->task_lock); + __iscsi_send_nop_out(session, htonl(itt), ISCSI_RSVD_TASK_TAG, NULL); +} + +/* send replies for NopIns that requested them */ +void +iscsi_send_nop_replys(struct iscsi_session *session) +{ + struct iscsi_nop_info *nop_info; + /* + * these aren't really tasks, but it's not worth having + * a separate lock for them + */ + spin_lock_bh(&session->task_lock); + /* + * space for one data-less reply is preallocated in + * the session itself + */ + if (session->nop_reply.ttt != ISCSI_RSVD_TASK_TAG) { + spin_unlock_bh(&session->task_lock); + __iscsi_send_nop_out(session, ISCSI_RSVD_TASK_TAG, + session->nop_reply.ttt, + session->nop_reply.lun); + session->nop_reply.ttt = ISCSI_RSVD_TASK_TAG; + spin_lock_bh(&session->task_lock); + } + /* + * if we get multiple reply requests, or they have data, + * they'll get queued up + */ + while (!list_empty(&session->nop_reply_list)) { + nop_info = list_entry(session->nop_reply_list.next, + struct iscsi_nop_info, reply_list); + list_del_init(&nop_info->reply_list); + + spin_unlock_bh(&session->task_lock); + __iscsi_send_nop_out(session, ISCSI_RSVD_TASK_TAG, + nop_info->ttt, nop_info->lun); + kfree(nop_info); + if (signal_pending(current)) + return; + spin_lock_bh(&session->task_lock); + } + spin_unlock_bh(&session->task_lock); +} + +void +iscsi_send_logout(struct iscsi_session *session) +{ + struct iscsi_logout_hdr stlh; + u32 itt; + int rc; + + spin_lock_bh(&session->task_lock); + itt = iscsi_alloc_itt(session); + spin_unlock_bh(&session->task_lock); + + memset(&stlh, 0, sizeof(stlh)); + stlh.opcode = ISCSI_OP_LOGOUT_CMD | ISCSI_OP_IMMEDIATE; + stlh.flags = ISCSI_FLAG_FINAL | (ISCSI_LOGOUT_REASON_CLOSE_SESSION & + ISCSI_FLAG_LOGOUT_REASON_MASK); + stlh.itt = htonl(itt); + stlh.cmdsn = htonl(session->cmd_sn); + stlh.expstatsn = htonl(session->exp_stat_sn); + + rc = iscsi_send_header(session, (struct iscsi_hdr *)&stlh, + session->header_digest); + if (rc != ISCSI_IO_SUCCESS) { + iscsi_host_err(session, "xmit_logout failed\n"); + /* drop here ? */ + iscsi_drop_session(session); + } +} + +/** + * iscsi_send_data_out - send a SCSI Data-out PDU + * @task: iscsi task + * @ttt: target transfer tag + * @data_offset: offset of transfer within the complete transfer + * @data_len: data trasnfer length + * + * Note: + * If command PDUs are small (no immediate data), we + * start new commands as soon as possible, so that we can + * overlap the R2T latency with the time it takes to + * send data for commands already issued. This increases + * throughput without significantly increasing the completion + * time of commands already issued. + **/ +static int +iscsi_send_data_out(struct iscsi_task *task, u32 ttt, u32 data_offset, + u32 data_len) +{ + struct iscsi_session *session = task->session; + struct scsi_cmnd *sc = task->scsi_cmnd; + struct scatterlist tmpsg, *sg; + struct iscsi_data_hdr stdh; + u32 data_sn = 0, dlen, remaining, sg_offset; + int i, rc = ISCSI_IO_SUCCESS; + + memset(&stdh, 0, sizeof(stdh)); + stdh.opcode = ISCSI_OP_SCSI_DATA; + stdh.itt = htonl(task->itt); + stdh.ttt = ttt; + + /* + * Find the right sg entry and offset into it if needed. + * Why do we not cache this index for DataPDUInOrder? + */ + sg_offset = data_offset; + sg = sc->request_buffer; + for (i = 0; i < sc->use_sg; i++) { + if (sg_offset < sg->length) + break; + else { + sg_offset -= sg->length; + sg++; + } + } + + /* + * check that the target did not send us some bad values. just + * let the cmnd timeout if it does. + */ + if (sc->request_bufflen < data_offset + data_len || + (sc->use_sg && i >= sc->use_sg)) { + iscsi_host_err(session, "iscsi_send_data_out - invalid write. " + "len %u, offset %u, request_bufflen %u, usg_sg " + "%u, task %u\n", data_len, data_offset, + sc->request_bufflen, sc->use_sg, task->itt); + return ISCSI_IO_INVALID_OP; + } + + /* + * PDU loop - might need to send multiple PDUs to satisfy + * the transfer, or we can also send a zero length PDU + */ + remaining = data_len; + do { + if (signal_pending(current)) { + rc = ISCSI_IO_INTR; + break; + } + + if (!session->immediate_data) + iscsi_run_pending_queue(session); + + stdh.datasn = htonl(data_sn++); + stdh.offset = htonl(data_offset); + stdh.expstatsn = htonl(session->exp_stat_sn); + + if (session->max_xmit_data_segment_len && + remaining > session->max_xmit_data_segment_len) + /* enforce the target's data segment limit */ + dlen = session->max_xmit_data_segment_len; + else { + /* final PDU of a data burst */ + dlen = remaining; + stdh.flags = ISCSI_FLAG_FINAL; + } + hton24(stdh.dlength, dlen); + + rc = iscsi_send_header(session, (struct iscsi_hdr *)&stdh, + session->header_digest); + if (rc != ISCSI_IO_SUCCESS) { + iscsi_drop_session(session); + break; + } + + if (sc->use_sg) + rc = iscsi_send_sg_data(session, sc->request_buffer, + &i, &sg_offset, sc->use_sg, + dlen, session->data_digest); + else { + sg_init_one(&tmpsg, sc->request_buffer, dlen); + rc = iscsi_send_sg_data(session, &tmpsg, &i, + &sg_offset, 1, dlen, + session->data_digest); + } + + if (rc != ISCSI_IO_SUCCESS && + rc != ISCSI_IO_INVALID_OP) + iscsi_drop_session(session); + + data_offset += dlen; + remaining -= dlen; + } while (remaining > 0 && rc == ISCSI_IO_SUCCESS); + + return rc; +} + +static inline unsigned +get_immediate_data_len(struct iscsi_session *session, struct scsi_cmnd *sc) +{ + int len; + + if (!session->immediate_data) + return 0; + + if (session->first_burst_len) + len = min(session->first_burst_len, + session->max_xmit_data_segment_len); + else + len = session->max_xmit_data_segment_len; + return min_t(unsigned, len, sc->request_bufflen); +} + +/* + * iscsi_queue_r2t may be called so the task lock must be held + * why not handle this in iscsi_send_scsi_cmnd? + */ +void +iscsi_queue_unsolicited_data(struct iscsi_task *task) +{ + unsigned imm_data_len; + struct iscsi_session *session = task->session; + struct scsi_cmnd *sc = task->scsi_cmnd; + + /* + * With ImmediateData, we may or may not have to send + * additional Data PDUs, depending on the amount of data, and + * the Max PDU Length, and the first_burst_len. + */ + if (!test_bit(ISCSI_TASK_WRITE, &task->flags) || + !sc->request_bufflen || session->initial_r2t) + return; + /* + * queue up unsolicited data PDUs. the implied initial R2T + * doesn't count against the MaxOutstandingR2T, so we can't use + * the normal R2T * fields of the task for the implied initial + * R2T. Use a special flag for the implied initial R2T, and + * let the rx thread update tasks in the tx_tasks collection + * if an R2T comes in before the implied initial R2T has been + * processed. + */ + if (session->immediate_data) { + imm_data_len = get_immediate_data_len(session, sc); + /* + * Only queue unsolicited data out PDUs if there is more + * data in the request, and the FirstBurstLength hasn't + * already been satisfied with the ImmediateData that + * will be sent below via iscsi_send_scsi_cmnd(). + */ + if (sc->request_bufflen == imm_data_len || + imm_data_len == session->first_burst_len) + return; + } + + __set_bit(ISCSI_TASK_INITIAL_R2T, &task->flags); + iscsi_queue_r2t(session, task); + set_bit(TX_DATA, &session->control_bits); + set_bit(TX_WAKE, &session->control_bits); +} + +/** + * iscsi_send_r2t_data - see if we need to send more data. + * @session: iscsi session + * + * Note: + * This may call iscsi_run_pending_queue under some conditions. + **/ +void +iscsi_send_r2t_data(struct iscsi_session *session) +{ + struct iscsi_task *task; + struct scsi_cmnd *sc; + u32 ttt, offset, len; + unsigned implied_len, imm_data_len; + int rc; + + spin_lock_bh(&session->task_lock); + retry: + task = iscsi_dequeue_r2t(session); + if (!task) + goto done; + + rc = ISCSI_IO_SUCCESS; + /* + * save the values that get set when we receive an R2T from + * the target, so that we can receive another one while + * we're sending data. + */ + ttt = task->ttt; + offset = task->data_offset; + len = task->data_length; + task->ttt = ISCSI_RSVD_TASK_TAG; + spin_unlock_bh(&session->task_lock); + + /* + * implied initial R2T + * (ISCSI_TASK_INITIAL_R2T bit is only accessed by tx + * thread so we do not need atomic ops) + */ + if (__test_and_clear_bit(ISCSI_TASK_INITIAL_R2T, &task->flags)) { + sc = task->scsi_cmnd; + /* + * FirstBurstLength == 0 means no limit when + * ImmediateData == 0 (not documented in README?) + */ + if (!session->first_burst_len) + implied_len = sc->request_bufflen; + else + implied_len = min_t(unsigned, session->first_burst_len, + sc->request_bufflen); + + if (session->immediate_data) { + imm_data_len = get_immediate_data_len(session, sc); + implied_len -= imm_data_len; + } else + imm_data_len = 0; + + rc = iscsi_send_data_out(task, ISCSI_RSVD_TASK_TAG, + imm_data_len, implied_len); + } + + /* normal R2T from the target */ + if (ttt != ISCSI_RSVD_TASK_TAG && rc == ISCSI_IO_SUCCESS) + iscsi_send_data_out(task, ttt, offset, len); + + spin_lock_bh(&session->task_lock); + __iscsi_put_task(task); + + if (!signal_pending(current)) + goto retry; + done: + spin_unlock_bh(&session->task_lock); +} + +/** + * iscsi_send_scsi_cmnd - Transmit iSCSI Command PDU. + * @task: iSCSI task to be transmitted + * + * Description: + * The header digest on the cmd PDU is calculated before sending the cmd. + * If ImmediateData is enabled, data digest is computed and data is sent + * along with cmd PDU. + **/ +void +iscsi_send_scsi_cmnd(struct iscsi_task *task) +{ + struct iscsi_scsi_cmd_hdr stsch; + struct iscsi_session *session = task->session; + struct scsi_cmnd *sc = task->scsi_cmnd; + int rc, first_sg = 0; + struct scatterlist tmpsg; + u32 imm_data_len = 0, sg_offset = 0; + + memset(&stsch, 0, sizeof(stsch)); + if (test_bit(ISCSI_TASK_READ, &task->flags)) { + stsch.flags |= ISCSI_FLAG_CMD_READ; + stsch.data_length = htonl(sc->request_bufflen); + } else if (test_bit(ISCSI_TASK_WRITE, &task->flags)) { + stsch.flags |= ISCSI_FLAG_CMD_WRITE; + stsch.data_length = htonl(sc->request_bufflen); + } + /* tagged command queueing */ + stsch.flags |= (iscsi_command_attr(sc) & ISCSI_FLAG_CMD_ATTR_MASK); + stsch.opcode = ISCSI_OP_SCSI_CMD; + stsch.itt = htonl(task->itt); + task->cmdsn = session->cmd_sn; + stsch.cmdsn = htonl(session->cmd_sn); + stsch.expstatsn = htonl(session->exp_stat_sn); + /* + * set the final bit when there are no unsolicited Data-out + * PDUs following the command PDU + */ + if (!test_bit(ISCSI_TASK_INITIAL_R2T, &task->flags)) + stsch.flags |= ISCSI_FLAG_FINAL; + /* single level LUN format puts LUN in byte 1, 0 everywhere else */ + stsch.lun[1] = sc->device->lun; + memcpy(stsch.scb, sc->cmnd, min_t(size_t, sizeof(stsch.scb), + sc->cmd_len)); + + if (session->immediate_data && + sc->sc_data_direction == DMA_TO_DEVICE) { + if (!sc->request_bufflen) + /* zero len write? just let it timeout */ + return; + + imm_data_len = get_immediate_data_len(session, sc); + /* put the data length in the PDU header */ + hton24(stsch.dlength, imm_data_len); + stsch.data_length = htonl(sc->request_bufflen); + } + + rc = iscsi_send_header(session, (struct iscsi_hdr *)&stsch, + session->header_digest); + if (rc != ISCSI_IO_SUCCESS) { + iscsi_host_err(session, "iscsi_send_scsi_cmnd failed to send " + "scsi cmnd header\n"); + iscsi_drop_session(session); + return; + } + + if (!imm_data_len) + goto done; + + if (sc->use_sg) + rc = iscsi_send_sg_data(session, sc->request_buffer, + &first_sg, &sg_offset, sc->use_sg, + imm_data_len, session->data_digest); + else { + sg_init_one(&tmpsg, sc->request_buffer, imm_data_len); + rc = iscsi_send_sg_data(session, &tmpsg, &first_sg, + &sg_offset, 1, imm_data_len, + session->data_digest); + } + + if (rc != ISCSI_IO_SUCCESS) { + iscsi_host_err(session, "iscsi_send_scsi_cmnd failed to send " + "scsi cmnd data (%u bytes)\n", imm_data_len); + if (rc != ISCSI_IO_INVALID_OP) + iscsi_drop_session(session); + } + done: + session->cmd_sn++; +} diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/Kconfig linux-2.6.9.work/drivers/scsi/iscsi_sfnet/Kconfig --- linux-2.6.9/drivers/scsi/iscsi_sfnet/Kconfig 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/Kconfig 2005-06-15 18:21:52.159881754 -0500 @@ -0,0 +1,26 @@ +config SCSI_ISCSI_SFNET + tristate "Software iSCSI support" + depends on SCSI && INET + select SCSI_ISCSI_ATTRS + select CRYPTO + select CRYPTO_MD5 + select CRYPTO_CRC32C + ---help--- + To compile this driver as a module, choose M here: the + module will be called iscsi_sfnet. + + The iSCSI Driver provides a host with the ability to access + storage through an IP network. The driver uses the iSCSI + protocol to transport SCSI requests and responses over an IP + network between the host (the "initiator") and "targets". + Architecturally, the iSCSI driver combines with the host's + TCP/IP stack, network drivers, and Network Interface Card + (NIC) to provide the same functions as a SCSI or a Fibre + Channel (FC) adapter driver with a Host Bus Adapter (HBA). + + The userspace component needed to initialize the driver, + documentation, and sample configuration files are in the + iscsi-initiator-utils package. + + More information on this driver can be found here: + http://linux-iscsi.sourceforge.net diff -Naurp linux-2.6.9/drivers/scsi/iscsi_sfnet/Makefile linux-2.6.9.work/drivers/scsi/iscsi_sfnet/Makefile --- linux-2.6.9/drivers/scsi/iscsi_sfnet/Makefile 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/iscsi_sfnet/Makefile 2005-06-15 17:38:13.487930537 -0500 @@ -0,0 +1,16 @@ +# +# Makefile for Software iSCSI driver +# +obj-$(CONFIG_SCSI_ISCSI_SFNET) += iscsi_sfnet.o +iscsi_sfnet-objs := iscsi-initiator.o +iscsi_sfnet-objs += iscsi-attr.o \ + iscsi-portal.o \ + iscsi-session.o \ + iscsi-task.o \ + iscsi-ioctl.o \ + iscsi-network.o \ + iscsi-recv-pdu.o \ + iscsi-xmit-pdu.o \ + iscsi-login.o \ + iscsi-auth.o \ + iscsi-auth-client.o diff -Naurp linux-2.6.9/drivers/scsi/Kconfig linux-2.6.9.work/drivers/scsi/Kconfig --- linux-2.6.9/drivers/scsi/Kconfig 2005-06-15 18:07:26.746196881 -0500 +++ linux-2.6.9.work/drivers/scsi/Kconfig 2005-06-15 16:57:44.582529915 -0500 @@ -209,6 +209,14 @@ config SCSI_FC_ATTRS each attached FiberChannel device to sysfs, say Y. Otherwise, say N. +config SCSI_ISCSI_ATTRS + tristate "iSCSI Transport Attributes" + depends on SCSI + help + If you wish to export transport-specific information about + each attached iSCSI device to sysfs, say Y. + Otherwise, say N. + endmenu menu "SCSI low-level drivers" @@ -824,6 +832,8 @@ config SCSI_INIA100 To compile this driver as a module, choose M here: the module will be called a100u2w. +source "drivers/scsi/iscsi_sfnet/Kconfig" + config SCSI_PPA tristate "IOMEGA parallel port (ppa - older drives)" depends on SCSI && PARPORT diff -Naurp linux-2.6.9/drivers/scsi/Makefile linux-2.6.9.work/drivers/scsi/Makefile --- linux-2.6.9/drivers/scsi/Makefile 2005-06-15 18:07:26.747196742 -0500 +++ linux-2.6.9.work/drivers/scsi/Makefile 2005-06-15 16:56:44.864900081 -0500 @@ -28,7 +28,7 @@ obj-$(CONFIG_SCSI) += scsi_mod.o # -------------------------- obj-$(CONFIG_SCSI_SPI_ATTRS) += scsi_transport_spi.o obj-$(CONFIG_SCSI_FC_ATTRS) += scsi_transport_fc.o - +obj-$(CONFIG_SCSI_ISCSI_ATTRS) += scsi_transport_iscsi.o obj-$(CONFIG_SCSI_AMIGA7XX) += amiga7xx.o 53c7xx.o obj-$(CONFIG_A3000_SCSI) += a3000.o wd33c93.o @@ -101,6 +101,7 @@ obj-$(CONFIG_SCSI_ACARD) += atp870u.o obj-$(CONFIG_SCSI_SUNESP) += esp.o obj-$(CONFIG_SCSI_GDTH) += gdth.o obj-$(CONFIG_SCSI_INITIO) += initio.o +obj-$(CONFIG_SCSI_ISCSI_SFNET) += iscsi_sfnet/ obj-$(CONFIG_SCSI_INIA100) += a100u2w.o obj-$(CONFIG_SCSI_QLOGICPTI) += qlogicpti.o obj-$(CONFIG_BLK_DEV_IDESCSI) += ide-scsi.o diff -Naurp linux-2.6.9/drivers/scsi/scsi_transport_iscsi.c linux-2.6.9.work/drivers/scsi/scsi_transport_iscsi.c --- linux-2.6.9/drivers/scsi/scsi_transport_iscsi.c 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/drivers/scsi/scsi_transport_iscsi.c 2005-06-15 17:33:07.062361901 -0500 @@ -0,0 +1,357 @@ +/* + * iSCSI transport class definitions + * + * Copyright (C) IBM Corporation, 2004 + * Copyright (C) Mike Christie, 2004 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include +#include +#include +#include +#include +#include + +#define ISCSI_SESSION_ATTRS 21 +#define ISCSI_HOST_ATTRS 2 + +struct iscsi_internal { + struct scsi_transport_template t; + struct iscsi_function_template *fnt; + /* + * We do not have any private or other attrs. + */ + struct class_device_attribute *session_attrs[ISCSI_SESSION_ATTRS + 1]; + struct class_device_attribute *host_attrs[ISCSI_HOST_ATTRS + 1]; +}; + +#define to_iscsi_internal(tmpl) container_of(tmpl, struct iscsi_internal, t) + +static void iscsi_transport_class_release(struct class_device *class_dev) +{ + struct scsi_target *starget = transport_class_to_starget(class_dev); + put_device(&starget->dev); +} + +struct class iscsi_transport_class = { + .name = "iscsi_transport", + .release = iscsi_transport_class_release, +}; + +static void iscsi_host_class_release(struct class_device *class_dev) +{ + struct Scsi_Host *shost = transport_class_to_shost(class_dev); + put_device(&shost->shost_gendev); +} + +struct class iscsi_host_class = { + .name = "iscsi_host", + .release = iscsi_host_class_release, +}; + +/* + * iSCSI target and session attrs + */ +#define iscsi_session_show_fn(field, format) \ + \ +static ssize_t \ +show_session_##field(struct class_device *cdev, char *buf) \ +{ \ + struct scsi_target *starget = transport_class_to_starget(cdev); \ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); \ + struct iscsi_internal *i = to_iscsi_internal(shost->transportt); \ + \ + if (i->fnt->get_##field) \ + i->fnt->get_##field(starget); \ + return snprintf(buf, 20, format"\n", iscsi_##field(starget)); \ +} + +#define iscsi_session_rd_attr(field, format) \ + iscsi_session_show_fn(field, format) \ +static CLASS_DEVICE_ATTR(field, S_IRUGO, show_session_##field, NULL); + +iscsi_session_rd_attr(tpgt, "%hu"); +iscsi_session_rd_attr(tsih, "%2x"); +iscsi_session_rd_attr(max_recv_data_segment_len, "%u"); +iscsi_session_rd_attr(max_xmit_data_segment_len, "%u"); +iscsi_session_rd_attr(max_burst_len, "%u"); +iscsi_session_rd_attr(first_burst_len, "%u"); +iscsi_session_rd_attr(def_time2wait, "%hu"); +iscsi_session_rd_attr(def_time2retain, "%hu"); +iscsi_session_rd_attr(max_outstanding_r2t, "%hu"); +iscsi_session_rd_attr(erl, "%d"); + + +#define iscsi_session_show_bool_fn(field) \ + \ +static ssize_t \ +show_session_bool_##field(struct class_device *cdev, char *buf) \ +{ \ + struct scsi_target *starget = transport_class_to_starget(cdev); \ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); \ + struct iscsi_internal *i = to_iscsi_internal(shost->transportt); \ + \ + if (i->fnt->get_##field) \ + i->fnt->get_##field(starget); \ + \ + if (iscsi_##field(starget)) \ + return sprintf(buf, "Yes\n"); \ + return sprintf(buf, "No\n"); \ +} + +#define iscsi_session_rd_bool_attr(field) \ + iscsi_session_show_bool_fn(field) \ +static CLASS_DEVICE_ATTR(field, S_IRUGO, show_session_bool_##field, NULL); + +iscsi_session_rd_bool_attr(initial_r2t); +iscsi_session_rd_bool_attr(immediate_data); +iscsi_session_rd_bool_attr(data_pdu_in_order); +iscsi_session_rd_bool_attr(data_sequence_in_order); + +#define iscsi_session_show_digest_fn(field) \ + \ +static ssize_t \ +show_##field(struct class_device *cdev, char *buf) \ +{ \ + struct scsi_target *starget = transport_class_to_starget(cdev); \ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); \ + struct iscsi_internal *i = to_iscsi_internal(shost->transportt); \ + \ + if (i->fnt->get_##field) \ + i->fnt->get_##field(starget); \ + \ + if (iscsi_##field(starget)) \ + return sprintf(buf, "CRC32C\n"); \ + return sprintf(buf, "None\n"); \ +} + +#define iscsi_session_rd_digest_attr(field) \ + iscsi_session_show_digest_fn(field) \ +static CLASS_DEVICE_ATTR(field, S_IRUGO, show_##field, NULL); + +iscsi_session_rd_digest_attr(header_digest); +iscsi_session_rd_digest_attr(data_digest); + +static ssize_t +show_port(struct class_device *cdev, char *buf) +{ + struct scsi_target *starget = transport_class_to_starget(cdev); + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); + struct iscsi_internal *i = to_iscsi_internal(shost->transportt); + + if (i->fnt->get_port) + i->fnt->get_port(starget); + + return snprintf(buf, 20, "%hu\n", ntohs(iscsi_port(starget))); +} +static CLASS_DEVICE_ATTR(port, S_IRUGO, show_port, NULL); + +static ssize_t +show_ip_address(struct class_device *cdev, char *buf) +{ + struct scsi_target *starget = transport_class_to_starget(cdev); + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); + struct iscsi_internal *i = to_iscsi_internal(shost->transportt); + + if (i->fnt->get_ip_address) + i->fnt->get_ip_address(starget); + + if (iscsi_addr_type(starget) == AF_INET) + return sprintf(buf, "%u.%u.%u.%u\n", + NIPQUAD(iscsi_sin_addr(starget))); + else if(iscsi_addr_type(starget) == AF_INET6) + return sprintf(buf, "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n", + NIP6(iscsi_sin6_addr(starget))); + return -EINVAL; +} +static CLASS_DEVICE_ATTR(ip_address, S_IRUGO, show_ip_address, NULL); + +static ssize_t +show_isid(struct class_device *cdev, char *buf) +{ + struct scsi_target *starget = transport_class_to_starget(cdev); + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); + struct iscsi_internal *i = to_iscsi_internal(shost->transportt); + + if (i->fnt->get_isid) + i->fnt->get_isid(starget); + + return sprintf(buf, "%02x%02x%02x%02x%02x%02x\n", + iscsi_isid(starget)[0], iscsi_isid(starget)[1], + iscsi_isid(starget)[2], iscsi_isid(starget)[3], + iscsi_isid(starget)[4], iscsi_isid(starget)[5]); +} +static CLASS_DEVICE_ATTR(isid, S_IRUGO, show_isid, NULL); + +/* + * This is used for iSCSI names. Normally, we follow + * the transport class convention of having the lld + * set the field, but in these cases the value is + * too large. + */ +#define iscsi_session_show_str_fn(field) \ + \ +static ssize_t \ +show_session_str_##field(struct class_device *cdev, char *buf) \ +{ \ + ssize_t ret = 0; \ + struct scsi_target *starget = transport_class_to_starget(cdev); \ + struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); \ + struct iscsi_internal *i = to_iscsi_internal(shost->transportt); \ + \ + if (i->fnt->get_##field) \ + ret = i->fnt->get_##field(starget, buf, PAGE_SIZE); \ + return ret; \ +} + +#define iscsi_session_rd_str_attr(field) \ + iscsi_session_show_str_fn(field) \ +static CLASS_DEVICE_ATTR(field, S_IRUGO, show_session_str_##field, NULL); + +iscsi_session_rd_str_attr(target_name); +iscsi_session_rd_str_attr(target_alias); + +/* + * iSCSI host attrs + */ + +/* + * Again, this is used for iSCSI names. Normally, we follow + * the transport class convention of having the lld set + * the field, but in these cases the value is too large. + */ +#define iscsi_host_show_str_fn(field) \ + \ +static ssize_t \ +show_host_str_##field(struct class_device *cdev, char *buf) \ +{ \ + int ret = 0; \ + struct Scsi_Host *shost = transport_class_to_shost(cdev); \ + struct iscsi_internal *i = to_iscsi_internal(shost->transportt); \ + \ + if (i->fnt->get_##field) \ + ret = i->fnt->get_##field(shost, buf, PAGE_SIZE); \ + return ret; \ +} + +#define iscsi_host_rd_str_attr(field) \ + iscsi_host_show_str_fn(field) \ +static CLASS_DEVICE_ATTR(field, S_IRUGO, show_host_str_##field, NULL); + +iscsi_host_rd_str_attr(initiator_name); +iscsi_host_rd_str_attr(initiator_alias); + +#define SETUP_SESSION_RD_ATTR(field) \ + if (i->fnt->show_##field) { \ + i->session_attrs[count] = &class_device_attr_##field; \ + count++; \ + } + +#define SETUP_HOST_RD_ATTR(field) \ + if (i->fnt->show_##field) { \ + i->host_attrs[count] = &class_device_attr_##field; \ + count++; \ + } + +struct scsi_transport_template * +iscsi_attach_transport(struct iscsi_function_template *fnt) +{ + struct iscsi_internal *i = kmalloc(sizeof(struct iscsi_internal), + GFP_KERNEL); + int count = 0; + + if (unlikely(!i)) + return NULL; + + memset(i, 0, sizeof(struct iscsi_internal)); + i->fnt = fnt; + + i->t.target_attrs = &i->session_attrs[0]; + i->t.target_class = &iscsi_transport_class; + i->t.target_setup = NULL; + i->t.target_size = sizeof(struct iscsi_class_session); + + SETUP_SESSION_RD_ATTR(tsih); + SETUP_SESSION_RD_ATTR(isid); + SETUP_SESSION_RD_ATTR(header_digest); + SETUP_SESSION_RD_ATTR(data_digest); + SETUP_SESSION_RD_ATTR(target_name); + SETUP_SESSION_RD_ATTR(target_alias); + SETUP_SESSION_RD_ATTR(port); + SETUP_SESSION_RD_ATTR(tpgt); + SETUP_SESSION_RD_ATTR(ip_address); + SETUP_SESSION_RD_ATTR(initial_r2t); + SETUP_SESSION_RD_ATTR(immediate_data); + SETUP_SESSION_RD_ATTR(max_recv_data_segment_len); + SETUP_SESSION_RD_ATTR(max_xmit_data_segment_len); + SETUP_SESSION_RD_ATTR(max_burst_len); + SETUP_SESSION_RD_ATTR(first_burst_len); + SETUP_SESSION_RD_ATTR(def_time2wait); + SETUP_SESSION_RD_ATTR(def_time2retain); + SETUP_SESSION_RD_ATTR(max_outstanding_r2t); + SETUP_SESSION_RD_ATTR(data_pdu_in_order); + SETUP_SESSION_RD_ATTR(data_sequence_in_order); + SETUP_SESSION_RD_ATTR(erl); + + BUG_ON(count > ISCSI_SESSION_ATTRS); + i->session_attrs[count] = NULL; + + i->t.host_attrs = &i->host_attrs[0]; + i->t.host_class = &iscsi_host_class; + i->t.host_setup = NULL; + i->t.host_size = 0; + + count = 0; + SETUP_HOST_RD_ATTR(initiator_name); + SETUP_HOST_RD_ATTR(initiator_alias); + + BUG_ON(count > ISCSI_HOST_ATTRS); + i->host_attrs[count] = NULL; + + return &i->t; +} + +EXPORT_SYMBOL(iscsi_attach_transport); + +void iscsi_release_transport(struct scsi_transport_template *t) +{ + struct iscsi_internal *i = to_iscsi_internal(t); + kfree(i); +} + +EXPORT_SYMBOL(iscsi_release_transport); + +static __init int iscsi_transport_init(void) +{ + int err = class_register(&iscsi_transport_class); + + if (err) + return err; + return class_register(&iscsi_host_class); +} + +static void __exit iscsi_transport_exit(void) +{ + class_unregister(&iscsi_host_class); + class_unregister(&iscsi_transport_class); +} + +module_init(iscsi_transport_init); +module_exit(iscsi_transport_exit); + +MODULE_AUTHOR("Mike Christie"); +MODULE_DESCRIPTION("iSCSI Transport Attributes"); +MODULE_LICENSE("GPL"); diff -Naurp linux-2.6.9/include/scsi/scsi_transport_iscsi.h linux-2.6.9.work/include/scsi/scsi_transport_iscsi.h --- linux-2.6.9/include/scsi/scsi_transport_iscsi.h 1969-12-31 18:00:00.000000000 -0600 +++ linux-2.6.9.work/include/scsi/scsi_transport_iscsi.h 2005-06-15 17:18:42.434206328 -0500 @@ -0,0 +1,183 @@ +/* + * iSCSI transport class definitions + * + * Copyright (C) IBM Corporation, 2004 + * Copyright (C) Mike Christie, 2004 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef SCSI_TRANSPORT_ISCSI_H +#define SCSI_TRANSPORT_ISCSI_H + +#include +#include +#include + +struct scsi_transport_template; + +struct iscsi_class_session { + uint8_t isid[6]; + uint16_t tsih; + int header_digest; /* 1 CRC32, 0 None */ + int data_digest; /* 1 CRC32, 0 None */ + uint16_t tpgt; + union { + struct in6_addr sin6_addr; + struct in_addr sin_addr; + } u; + sa_family_t addr_type; /* must be AF_INET or AF_INET6 */ + uint16_t port; /* must be in network byte order */ + int initial_r2t; /* 1 Yes, 0 No */ + int immediate_data; /* 1 Yes, 0 No */ + uint32_t max_recv_data_segment_len; + uint32_t max_xmit_data_segment_len; + uint32_t max_burst_len; + uint32_t first_burst_len; + uint16_t def_time2wait; + uint16_t def_time2retain; + uint16_t max_outstanding_r2t; + int data_pdu_in_order; /* 1 Yes, 0 No */ + int data_sequence_in_order; /* 1 Yes, 0 No */ + int erl; +}; + +/* + * accessor macros + */ +#define iscsi_isid(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->isid) +#define iscsi_tsih(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->tsih) +#define iscsi_header_digest(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->header_digest) +#define iscsi_data_digest(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->data_digest) +#define iscsi_port(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->port) +#define iscsi_addr_type(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->addr_type) +#define iscsi_sin_addr(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->u.sin_addr) +#define iscsi_sin6_addr(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->u.sin6_addr) +#define iscsi_tpgt(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->tpgt) +#define iscsi_initial_r2t(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->initial_r2t) +#define iscsi_immediate_data(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->immediate_data) +#define iscsi_max_recv_data_segment_len(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->max_recv_data_segment_len) +#define iscsi_max_xmit_data_segment_len(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->max_xmit_data_segment_len) +#define iscsi_max_burst_len(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->max_burst_len) +#define iscsi_first_burst_len(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->first_burst_len) +#define iscsi_def_time2wait(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->def_time2wait) +#define iscsi_def_time2retain(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->def_time2retain) +#define iscsi_max_outstanding_r2t(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->max_outstanding_r2t) +#define iscsi_data_pdu_in_order(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->data_pdu_in_order) +#define iscsi_data_sequence_in_order(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->data_sequence_in_order) +#define iscsi_erl(x) \ + (((struct iscsi_class_session *)&(x)->starget_data)->erl) + +/* + * The functions by which the transport class and the driver communicate + */ +struct iscsi_function_template { + /* + * target attrs + */ + void (*get_isid)(struct scsi_target *); + void (*get_tsih)(struct scsi_target *); + void (*get_header_digest)(struct scsi_target *); + void (*get_data_digest)(struct scsi_target *); + void (*get_port)(struct scsi_target *); + void (*get_tpgt)(struct scsi_target *); + /* + * In get_ip_address the lld must set the address and + * the address type + */ + void (*get_ip_address)(struct scsi_target *); + /* + * The lld should snprintf the name or alias to the buffer + */ + ssize_t (*get_target_name)(struct scsi_target *, char *, ssize_t); + ssize_t (*get_target_alias)(struct scsi_target *, char *, ssize_t); + void (*get_initial_r2t)(struct scsi_target *); + void (*get_immediate_data)(struct scsi_target *); + void (*get_max_recv_data_segment_len)(struct scsi_target *); + void (*get_max_xmit_data_segment_len)(struct scsi_target *); + void (*get_max_burst_len)(struct scsi_target *); + void (*get_first_burst_len)(struct scsi_target *); + void (*get_def_time2wait)(struct scsi_target *); + void (*get_def_time2retain)(struct scsi_target *); + void (*get_max_outstanding_r2t)(struct scsi_target *); + void (*get_data_pdu_in_order)(struct scsi_target *); + void (*get_data_sequence_in_order)(struct scsi_target *); + void (*get_erl)(struct scsi_target *); + + /* + * host atts + */ + + /* + * The lld should snprintf the name or alias to the buffer + */ + ssize_t (*get_initiator_alias)(struct Scsi_Host *, char *, ssize_t); + ssize_t (*get_initiator_name)(struct Scsi_Host *, char *, ssize_t); + /* + * The driver sets these to tell the transport class it + * wants the attributes displayed in sysfs. If the show_ flag + * is not set, the attribute will be private to the transport + * class. We could probably just test if a get_ fn was set + * since we only use the values for sysfs but this is how + * fc does it too. + */ + unsigned long show_isid:1; + unsigned long show_tsih:1; + unsigned long show_header_digest:1; + unsigned long show_data_digest:1; + unsigned long show_port:1; + unsigned long show_tpgt:1; + unsigned long show_ip_address:1; + unsigned long show_target_name:1; + unsigned long show_target_alias:1; + unsigned long show_initial_r2t:1; + unsigned long show_immediate_data:1; + unsigned long show_max_recv_data_segment_len:1; + unsigned long show_max_xmit_data_segment_len:1; + unsigned long show_max_burst_len:1; + unsigned long show_first_burst_len:1; + unsigned long show_def_time2wait:1; + unsigned long show_def_time2retain:1; + unsigned long show_max_outstanding_r2t:1; + unsigned long show_data_pdu_in_order:1; + unsigned long show_data_sequence_in_order:1; + unsigned long show_erl:1; + unsigned long show_initiator_name:1; + unsigned long show_initiator_alias:1; +}; + +struct scsi_transport_template *iscsi_attach_transport(struct iscsi_function_template *); +void iscsi_release_transport(struct scsi_transport_template *); + +#endif