int iscsit_recover_from_unknown_opcode(struct iscsi_conn *conn)
{
	if (conn->of_marker > (conn->conn_ops->OFMarkInt * 4)) {
		pr_err("Remaining bytes to OFMarker: %u exceeds"
			" OFMarkInt bytes: %u.\n", conn->of_marker,
				conn->conn_ops->OFMarkInt * 4);
		return -1;
	}

	pr_debug("Advancing %u bytes in TCP stream to get to the"
			" next OFMarker.\n", conn->of_marker);

	if (iscsit_dump_data_payload(conn, conn->of_marker, 0) < 0)
		return -1;

	if (conn->of_marker_offset > (ISCSI_HDR_LEN + (ISCSI_CRC_LEN * 2) +
	    conn->conn_ops->MaxRecvDataSegmentLength)) {
		pr_err("OfMarker offset value: %u exceeds limit.\n",
			conn->of_marker_offset);
		return -1;
	}

	pr_debug("Discarding %u bytes of TCP stream to get to the"
			" next iSCSI Opcode.\n", conn->of_marker_offset);

	if (iscsit_dump_data_payload(conn, conn->of_marker_offset, 0) < 0)
		return -1;

	return 0;
}
static int iscsit_dataout_pre_datapduinorder_no(
	struct iscsi_cmd *cmd,
	unsigned char *buf)
{
	struct iscsi_pdu *pdu;
	struct iscsi_data *hdr = (struct iscsi_data *) buf;
	u32 payload_length = ntoh24(hdr->dlength);

	pdu = iscsit_get_pdu_holder(cmd, be32_to_cpu(hdr->offset),
				    payload_length);
	if (!pdu)
		return DATAOUT_CANNOT_RECOVER;

	cmd->pdu_ptr = pdu;

	switch (pdu->status) {
	case ISCSI_PDU_NOT_RECEIVED:
	case ISCSI_PDU_CRC_FAILED:
	case ISCSI_PDU_TIMED_OUT:
		break;
	case ISCSI_PDU_RECEIVED_OK:
		pr_err("Command ITT: 0x%08x received already gotten"
			" Offset: %u, Length: %u\n", cmd->init_task_tag,
				be32_to_cpu(hdr->offset), payload_length);
		return iscsit_dump_data_payload(cmd->conn, payload_length, 1);
	default:
		return DATAOUT_CANNOT_RECOVER;
	}

	return DATAOUT_NORMAL;
}
Esempio n. 3
0
static int iscsit_dataout_pre_datapduinorder_yes(
	struct iscsi_cmd *cmd,
	unsigned char *buf)
{
	int dump = 0, recovery = 0;
	struct iscsi_conn *conn = cmd->conn;
	struct iscsi_data *hdr = (struct iscsi_data *) buf;
	u32 payload_length = ntoh24(hdr->dlength);

	/*
	 * For DataSequenceInOrder=Yes: If the offset is greater than the global
	 * DataPDUInOrder=Yes offset counter in struct iscsi_cmd a protcol error has
	 * occured and fail the connection.
	 *
	 * For DataSequenceInOrder=No: If the offset is greater than the per
	 * sequence DataPDUInOrder=Yes offset counter in struct iscsi_seq a protocol
	 * error has occured and fail the connection.
	 */
	if (conn->sess->sess_ops->DataSequenceInOrder) {
		if (hdr->offset != cmd->write_data_done) {
			pr_err("Command ITT: 0x%08x, received offset"
			" %u different than expected %u.\n", cmd->init_task_tag,
				hdr->offset, cmd->write_data_done);
			recovery = 1;
			goto recover;
		}
	} else {
		struct iscsi_seq *seq = cmd->seq_ptr;

		if (hdr->offset > seq->offset) {
			pr_err("Command ITT: 0x%08x, received offset"
			" %u greater than expected %u.\n", cmd->init_task_tag,
				hdr->offset, seq->offset);
			recovery = 1;
			goto recover;
		} else if (hdr->offset < seq->offset) {
			pr_err("Command ITT: 0x%08x, received offset"
			" %u less than expected %u, discarding payload.\n",
				cmd->init_task_tag, hdr->offset, seq->offset);
			dump = 1;
			goto dump;
		}
	}

	return DATAOUT_NORMAL;

recover:
	if (!conn->sess->sess_ops->ErrorRecoveryLevel) {
		pr_err("Unable to perform within-command recovery"
				" while ERL=0.\n");
		return DATAOUT_CANNOT_RECOVER;
	}
dump:
	if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
		return DATAOUT_CANNOT_RECOVER;

	return (recovery) ? iscsit_recover_dataout_sequence(cmd,
		hdr->offset, payload_length) :
	       (dump) ? DATAOUT_WITHIN_COMMAND_RECOVERY : DATAOUT_NORMAL;
}
static int iscsit_dataout_check_datasn(
	struct iscsi_cmd *cmd,
	unsigned char *buf)
{
	int dump = 0, recovery = 0;
	u32 data_sn = 0;
	struct iscsi_conn *conn = cmd->conn;
	struct iscsi_data *hdr = (struct iscsi_data *) buf;
	u32 payload_length = ntoh24(hdr->dlength);

	/*
	 * Considering the target has no method of re-requesting DataOUT
	 * by DataSN, if we receieve a greater DataSN than expected we
	 * assume the functions for DataPDUInOrder=[Yes,No] below will
	 * handle it.
	 *
	 * If the DataSN is less than expected, dump the payload.
	 */
	if (conn->sess->sess_ops->DataSequenceInOrder)
		data_sn = cmd->data_sn;
	else {
		struct iscsi_seq *seq = cmd->seq_ptr;
		data_sn = seq->data_sn;
	}

	if (be32_to_cpu(hdr->datasn) > data_sn) {
		pr_err("Command ITT: 0x%08x, received DataSN: 0x%08x"
			" higher than expected 0x%08x.\n", cmd->init_task_tag,
				be32_to_cpu(hdr->datasn), data_sn);
		recovery = 1;
		goto recover;
	} else if (be32_to_cpu(hdr->datasn) < data_sn) {
		pr_err("Command ITT: 0x%08x, received DataSN: 0x%08x"
			" lower than expected 0x%08x, discarding payload.\n",
			cmd->init_task_tag, be32_to_cpu(hdr->datasn), data_sn);
		dump = 1;
		goto dump;
	}

	return DATAOUT_NORMAL;

recover:
	if (!conn->sess->sess_ops->ErrorRecoveryLevel) {
		pr_err("Unable to perform within-command recovery"
				" while ERL=0.\n");
		return DATAOUT_CANNOT_RECOVER;
	}
dump:
	if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
		return DATAOUT_CANNOT_RECOVER;

	return (recovery || dump) ? DATAOUT_WITHIN_COMMAND_RECOVERY :
				DATAOUT_NORMAL;
}
static int iscsit_dataout_within_command_recovery_check(
	struct iscsi_cmd *cmd,
	unsigned char *buf)
{
	struct iscsi_conn *conn = cmd->conn;
	struct iscsi_data *hdr = (struct iscsi_data *) buf;
	u32 payload_length = ntoh24(hdr->dlength);

	if (conn->sess->sess_ops->DataSequenceInOrder) {
		if ((cmd->cmd_flags & ICF_WITHIN_COMMAND_RECOVERY) &&
		    (cmd->write_data_done != hdr->offset))
			goto dump;

		cmd->cmd_flags &= ~ICF_WITHIN_COMMAND_RECOVERY;
	} else {
		struct iscsi_seq *seq;

		seq = iscsit_get_seq_holder(cmd, hdr->offset, payload_length);
		if (!seq)
			return DATAOUT_CANNOT_RECOVER;
		cmd->seq_ptr = seq;

		if (conn->sess->sess_ops->DataPDUInOrder) {
			if ((seq->status ==
			     DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY) &&
			   ((seq->offset != hdr->offset) ||
			    (seq->data_sn != hdr->datasn)))
				goto dump;
		} else {
			if ((seq->status ==
			     DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY) &&
			    (seq->data_sn != hdr->datasn))
				goto dump;
		}

		if (seq->status == DATAOUT_SEQUENCE_COMPLETE)
			goto dump;

		if (seq->status != DATAOUT_SEQUENCE_COMPLETE)
			seq->status = 0;
	}

	return DATAOUT_NORMAL;

dump:
	pr_err("Dumping DataOUT PDU Offset: %u Length: %d DataSN:"
		" 0x%08x\n", hdr->offset, payload_length, hdr->datasn);
	return iscsit_dump_data_payload(conn, payload_length, 1);
}
static int iscsit_dataout_check_datasn(
	struct iscsi_cmd *cmd,
	unsigned char *buf)
{
	int dump = 0, recovery = 0;
	u32 data_sn = 0;
	struct iscsi_conn *conn = cmd->conn;
	struct iscsi_data *hdr = (struct iscsi_data *) buf;
	u32 payload_length = ntoh24(hdr->dlength);

	if (conn->sess->sess_ops->DataSequenceInOrder)
		data_sn = cmd->data_sn;
	else {
		struct iscsi_seq *seq = cmd->seq_ptr;
		data_sn = seq->data_sn;
	}

	if (hdr->datasn > data_sn) {
		pr_err("Command ITT: 0x%08x, received DataSN: 0x%08x"
			" higher than expected 0x%08x.\n", cmd->init_task_tag,
				hdr->datasn, data_sn);
		recovery = 1;
		goto recover;
	} else if (hdr->datasn < data_sn) {
		pr_err("Command ITT: 0x%08x, received DataSN: 0x%08x"
			" lower than expected 0x%08x, discarding payload.\n",
			cmd->init_task_tag, hdr->datasn, data_sn);
		dump = 1;
		goto dump;
	}

	return DATAOUT_NORMAL;

recover:
	if (!conn->sess->sess_ops->ErrorRecoveryLevel) {
		pr_err("Unable to perform within-command recovery"
				" while ERL=0.\n");
		return DATAOUT_CANNOT_RECOVER;
	}
dump:
	if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
		return DATAOUT_CANNOT_RECOVER;

	return (recovery || dump) ? DATAOUT_WITHIN_COMMAND_RECOVERY :
				DATAOUT_NORMAL;
}
static int iscsit_dataout_within_command_recovery_check(
	struct iscsi_cmd *cmd,
	unsigned char *buf)
{
	struct iscsi_conn *conn = cmd->conn;
	struct iscsi_data *hdr = (struct iscsi_data *) buf;
	u32 payload_length = ntoh24(hdr->dlength);

	/*
	 * We do the within-command recovery checks here as it is
	 * the first function called in iscsi_check_pre_dataout().
	 * Basically, if we are in within-command recovery and
	 * the PDU does not contain the offset the sequence needs,
	 * dump the payload.
	 *
	 * This only applies to DataPDUInOrder=Yes, for
	 * DataPDUInOrder=No we only re-request the failed PDU
	 * and check that all PDUs in a sequence are received
	 * upon end of sequence.
	 */
	if (conn->sess->sess_ops->DataSequenceInOrder) {
		if ((cmd->cmd_flags & ICF_WITHIN_COMMAND_RECOVERY) &&
		    cmd->write_data_done != be32_to_cpu(hdr->offset))
			goto dump;

		cmd->cmd_flags &= ~ICF_WITHIN_COMMAND_RECOVERY;
	} else {
		struct iscsi_seq *seq;

		seq = iscsit_get_seq_holder(cmd, be32_to_cpu(hdr->offset),
					    payload_length);
		if (!seq)
			return DATAOUT_CANNOT_RECOVER;
		/*
		 * Set the struct iscsi_seq pointer to reuse later.
		 */
		cmd->seq_ptr = seq;

		if (conn->sess->sess_ops->DataPDUInOrder) {
			if (seq->status ==
			    DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY &&
			   (seq->offset != be32_to_cpu(hdr->offset) ||
			    seq->data_sn != be32_to_cpu(hdr->datasn)))
				goto dump;
		} else {
			if (seq->status ==
			     DATAOUT_SEQUENCE_WITHIN_COMMAND_RECOVERY &&
			    seq->data_sn != be32_to_cpu(hdr->datasn))
				goto dump;
		}

		if (seq->status == DATAOUT_SEQUENCE_COMPLETE)
			goto dump;

		if (seq->status != DATAOUT_SEQUENCE_COMPLETE)
			seq->status = 0;
	}

	return DATAOUT_NORMAL;

dump:
	pr_err("Dumping DataOUT PDU Offset: %u Length: %d DataSN:"
		" 0x%08x\n", hdr->offset, payload_length, hdr->datasn);
	return iscsit_dump_data_payload(conn, payload_length, 1);
}
static int iscsit_dataout_check_sequence(
	struct iscsi_cmd *cmd,
	unsigned char *buf)
{
	u32 next_burst_len;
	struct iscsi_conn *conn = cmd->conn;
	struct iscsi_seq *seq = NULL;
	struct iscsi_data *hdr = (struct iscsi_data *) buf;
	u32 payload_length = ntoh24(hdr->dlength);

	/*
	 * For DataSequenceInOrder=Yes: Check that the offset and offset+length
	 * is within range as defined by iscsi_set_dataout_sequence_values().
	 *
	 * For DataSequenceInOrder=No: Check that an struct iscsi_seq exists for
	 * offset+length tuple.
	 */
	if (conn->sess->sess_ops->DataSequenceInOrder) {
		/*
		 * Due to possibility of recovery DataOUT sent by the initiator
		 * fullfilling an Recovery R2T, it's best to just dump the
		 * payload here, instead of erroring out.
		 */
		if ((be32_to_cpu(hdr->offset) < cmd->seq_start_offset) ||
		   ((be32_to_cpu(hdr->offset) + payload_length) > cmd->seq_end_offset)) {
			pr_err("Command ITT: 0x%08x with Offset: %u,"
			" Length: %u outside of Sequence %u:%u while"
			" DataSequenceInOrder=Yes.\n", cmd->init_task_tag,
			be32_to_cpu(hdr->offset), payload_length, cmd->seq_start_offset,
				cmd->seq_end_offset);

			if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
				return DATAOUT_CANNOT_RECOVER;
			return DATAOUT_WITHIN_COMMAND_RECOVERY;
		}

		next_burst_len = (cmd->next_burst_len + payload_length);
	} else {
		seq = iscsit_get_seq_holder(cmd, be32_to_cpu(hdr->offset),
					    payload_length);
		if (!seq)
			return DATAOUT_CANNOT_RECOVER;
		/*
		 * Set the struct iscsi_seq pointer to reuse later.
		 */
		cmd->seq_ptr = seq;

		if (seq->status == DATAOUT_SEQUENCE_COMPLETE) {
			if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
				return DATAOUT_CANNOT_RECOVER;
			return DATAOUT_WITHIN_COMMAND_RECOVERY;
		}

		next_burst_len = (seq->next_burst_len + payload_length);
	}

	if (next_burst_len > conn->sess->sess_ops->MaxBurstLength) {
		pr_err("Command ITT: 0x%08x, NextBurstLength: %u and"
			" Length: %u exceeds MaxBurstLength: %u. protocol"
			" error.\n", cmd->init_task_tag,
			(next_burst_len - payload_length),
			payload_length, conn->sess->sess_ops->MaxBurstLength);
		return DATAOUT_CANNOT_RECOVER;
	}

	/*
	 * Perform various MaxBurstLength and ISCSI_FLAG_CMD_FINAL sanity
	 * checks for the current DataOUT Sequence.
	 */
	if (hdr->flags & ISCSI_FLAG_CMD_FINAL) {
		/*
		 * Ignore ISCSI_FLAG_CMD_FINAL checks while DataPDUInOrder=No, end of
		 * sequence checks are handled in
		 * iscsit_dataout_datapduinorder_no_fbit().
		 */
		if (!conn->sess->sess_ops->DataPDUInOrder)
			goto out;

		if (conn->sess->sess_ops->DataSequenceInOrder) {
			if ((next_burst_len <
			     conn->sess->sess_ops->MaxBurstLength) &&
			   ((cmd->write_data_done + payload_length) <
			     cmd->se_cmd.data_length)) {
				pr_err("Command ITT: 0x%08x set ISCSI_FLAG_CMD_FINAL"
				" before end of DataOUT sequence, protocol"
				" error.\n", cmd->init_task_tag);
				return DATAOUT_CANNOT_RECOVER;
			}
		} else {
			if (next_burst_len < seq->xfer_len) {
				pr_err("Command ITT: 0x%08x set ISCSI_FLAG_CMD_FINAL"
				" before end of DataOUT sequence, protocol"
				" error.\n", cmd->init_task_tag);
				return DATAOUT_CANNOT_RECOVER;
			}
		}
	} else {
		if (conn->sess->sess_ops->DataSequenceInOrder) {
			if (next_burst_len ==
					conn->sess->sess_ops->MaxBurstLength) {
				pr_err("Command ITT: 0x%08x reached"
				" MaxBurstLength: %u, but ISCSI_FLAG_CMD_FINAL is"
				" not set, protocol error.", cmd->init_task_tag,
					conn->sess->sess_ops->MaxBurstLength);
				return DATAOUT_CANNOT_RECOVER;
			}
			if ((cmd->write_data_done + payload_length) ==
					cmd->se_cmd.data_length) {
				pr_err("Command ITT: 0x%08x reached"
				" last DataOUT PDU in sequence but ISCSI_FLAG_"
				"CMD_FINAL is not set, protocol error.\n",
					cmd->init_task_tag);
				return DATAOUT_CANNOT_RECOVER;
			}
		} else {
			if (next_burst_len == seq->xfer_len) {
				pr_err("Command ITT: 0x%08x reached"
				" last DataOUT PDU in sequence but ISCSI_FLAG_"
				"CMD_FINAL is not set, protocol error.\n",
					cmd->init_task_tag);
				return DATAOUT_CANNOT_RECOVER;
			}
		}
	}

out:
	return DATAOUT_NORMAL;
}
static int iscsit_dataout_check_sequence(
	struct iscsi_cmd *cmd,
	unsigned char *buf)
{
	u32 next_burst_len;
	struct iscsi_conn *conn = cmd->conn;
	struct iscsi_seq *seq = NULL;
	struct iscsi_data *hdr = (struct iscsi_data *) buf;
	u32 payload_length = ntoh24(hdr->dlength);

	if (conn->sess->sess_ops->DataSequenceInOrder) {
		if ((hdr->offset < cmd->seq_start_offset) ||
		   ((hdr->offset + payload_length) > cmd->seq_end_offset)) {
			pr_err("Command ITT: 0x%08x with Offset: %u,"
			" Length: %u outside of Sequence %u:%u while"
			" DataSequenceInOrder=Yes.\n", cmd->init_task_tag,
			hdr->offset, payload_length, cmd->seq_start_offset,
				cmd->seq_end_offset);

			if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
				return DATAOUT_CANNOT_RECOVER;
			return DATAOUT_WITHIN_COMMAND_RECOVERY;
		}

		next_burst_len = (cmd->next_burst_len + payload_length);
	} else {
		seq = iscsit_get_seq_holder(cmd, hdr->offset, payload_length);
		if (!seq)
			return DATAOUT_CANNOT_RECOVER;
		cmd->seq_ptr = seq;

		if (seq->status == DATAOUT_SEQUENCE_COMPLETE) {
			if (iscsit_dump_data_payload(conn, payload_length, 1) < 0)
				return DATAOUT_CANNOT_RECOVER;
			return DATAOUT_WITHIN_COMMAND_RECOVERY;
		}

		next_burst_len = (seq->next_burst_len + payload_length);
	}

	if (next_burst_len > conn->sess->sess_ops->MaxBurstLength) {
		pr_err("Command ITT: 0x%08x, NextBurstLength: %u and"
			" Length: %u exceeds MaxBurstLength: %u. protocol"
			" error.\n", cmd->init_task_tag,
			(next_burst_len - payload_length),
			payload_length, conn->sess->sess_ops->MaxBurstLength);
		return DATAOUT_CANNOT_RECOVER;
	}

	if (hdr->flags & ISCSI_FLAG_CMD_FINAL) {
		if (!conn->sess->sess_ops->DataPDUInOrder)
			goto out;

		if (conn->sess->sess_ops->DataSequenceInOrder) {
			if ((next_burst_len <
			     conn->sess->sess_ops->MaxBurstLength) &&
			   ((cmd->write_data_done + payload_length) <
			     cmd->data_length)) {
				pr_err("Command ITT: 0x%08x set ISCSI_FLAG_CMD_FINAL"
				" before end of DataOUT sequence, protocol"
				" error.\n", cmd->init_task_tag);
				return DATAOUT_CANNOT_RECOVER;
			}
		} else {
			if (next_burst_len < seq->xfer_len) {
				pr_err("Command ITT: 0x%08x set ISCSI_FLAG_CMD_FINAL"
				" before end of DataOUT sequence, protocol"
				" error.\n", cmd->init_task_tag);
				return DATAOUT_CANNOT_RECOVER;
			}
		}
	} else {
		if (conn->sess->sess_ops->DataSequenceInOrder) {
			if (next_burst_len ==
					conn->sess->sess_ops->MaxBurstLength) {
				pr_err("Command ITT: 0x%08x reached"
				" MaxBurstLength: %u, but ISCSI_FLAG_CMD_FINAL is"
				" not set, protocol error.", cmd->init_task_tag,
					conn->sess->sess_ops->MaxBurstLength);
				return DATAOUT_CANNOT_RECOVER;
			}
			if ((cmd->write_data_done + payload_length) ==
					cmd->data_length) {
				pr_err("Command ITT: 0x%08x reached"
				" last DataOUT PDU in sequence but ISCSI_FLAG_"
				"CMD_FINAL is not set, protocol error.\n",
					cmd->init_task_tag);
				return DATAOUT_CANNOT_RECOVER;
			}
		} else {
			if (next_burst_len == seq->xfer_len) {
				pr_err("Command ITT: 0x%08x reached"
				" last DataOUT PDU in sequence but ISCSI_FLAG_"
				"CMD_FINAL is not set, protocol error.\n",
					cmd->init_task_tag);
				return DATAOUT_CANNOT_RECOVER;
			}
		}
	}

out:
	return DATAOUT_NORMAL;
}