From mboxrd@z Thu Jan 1 00:00:00 1970 From: Mike Christie Subject: [PATCH 7/10][RFC] linux-iscsi driver Date: Mon, 10 Jan 2005 15:02:01 -0800 Message-ID: <41E30969.5050907@cs.wisc.edu> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="------------060300030100060507040307" Return-path: Received: from sabe.cs.wisc.edu ([128.105.6.20]:220 "EHLO sabe.cs.wisc.edu") by vger.kernel.org with ESMTP id S262750AbVAJXCJ (ORCPT ); Mon, 10 Jan 2005 18:02:09 -0500 Received: from [192.168.1.7] ([199.108.226.254]) (authenticated bits=0) by sabe.cs.wisc.edu (8.13.1/8.13.1) with ESMTP id j0AN265A024991 (version=TLSv1/SSLv3 cipher=RC4-MD5 bits=128 verify=NO) for ; Mon, 10 Jan 2005 17:02:07 -0600 Sender: linux-scsi-owner@vger.kernel.org List-Id: linux-scsi@vger.kernel.org To: linux-scsi@vger.kernel.org This is a multi-part message in MIME format. --------------060300030100060507040307 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Handles iscsi logins. --------------060300030100060507040307 Content-Type: text/x-patch; name="07-iscsi-login.patch" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="07-iscsi-login.patch" diff -Naurp scsi-misc-2.6.orig/drivers/scsi/iscsi-sfnet/iscsi-login.c scsi-misc-2.6.patch/drivers/scsi/iscsi-sfnet/iscsi-login.c --- scsi-misc-2.6.orig/drivers/scsi/iscsi-sfnet/iscsi-login.c 1969-12-31 16:00:00.000000000 -0800 +++ scsi-misc-2.6.patch/drivers/scsi/iscsi-sfnet/iscsi-login.c 2005-01-10 12:30:41.545574722 -0800 @@ -0,0 +1,1374 @@ +/* + * 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. + * + * 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") == 0)) + session->initial_r2t = 1; + else + session->initial_r2t = 0; + } 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") == 0) + session->data_pdu_in_order = 1; + else + session->data_pdu_in_order = 0; + } 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") == 0) + session->data_seq_in_order = 1; + else + session->data_seq_in_order = 0; + 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 = 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 = 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, + u8 *status_class, u8 *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->auth_client_block) { + 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 scsi-misc-2.6.orig/drivers/scsi/iscsi-sfnet/iscsi-login.h scsi-misc-2.6.patch/drivers/scsi/iscsi-sfnet/iscsi-login.h --- scsi-misc-2.6.orig/drivers/scsi/iscsi-sfnet/iscsi-login.h 1969-12-31 16:00:00.000000000 -0800 +++ scsi-misc-2.6.patch/drivers/scsi/iscsi-sfnet/iscsi-login.h 2005-01-10 12:30:41.546574590 -0800 @@ -0,0 +1,84 @@ +/* + * 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. + * + * 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 --------------060300030100060507040307--