private static KeyValuePairList <string, string> GetLoginResponseOperationalParameters(ConnectionParameters connection) { ISCSISession session = connection.Session; KeyValuePairList <string, string> loginParameters = new KeyValuePairList <string, string>(); loginParameters.Add("HeaderDigest", "None"); loginParameters.Add("DataDigest", "None"); loginParameters.Add("MaxRecvDataSegmentLength", connection.TargetMaxRecvDataSegmentLength.ToString()); if (!session.IsDiscovery) { loginParameters.Add("MaxConnections", session.MaxConnections.ToString()); loginParameters.Add("InitialR2T", session.InitialR2T ? "Yes" : "No"); // Microsoft iSCSI Target support InitialR2T = No loginParameters.Add("ImmediateData", session.ImmediateData ? "Yes" : "No"); loginParameters.Add("MaxBurstLength", session.MaxBurstLength.ToString()); loginParameters.Add("FirstBurstLength", session.FirstBurstLength.ToString()); loginParameters.Add("MaxOutstandingR2T", session.MaxOutstandingR2T.ToString()); loginParameters.Add("DataPDUInOrder", session.DataPDUInOrder ? "Yes" : "No"); loginParameters.Add("DataSequenceInOrder", session.DataSequenceInOrder ? "Yes" : "No"); loginParameters.Add("ErrorRecoveryLevel", session.ErrorRecoveryLevel.ToString()); } loginParameters.Add("DefaultTime2Wait", session.DefaultTime2Wait.ToString()); loginParameters.Add("DefaultTime2Retain", session.DefaultTime2Retain.ToString()); return(loginParameters); }
internal static List <ReadyToTransferPDU> GetReadyToTransferPDUs(SCSICommandPDU command, ConnectionParameters connection, out List <SCSICommandPDU> commandsToExecute) { // We return either SCSIResponsePDU or List<SCSIDataInPDU> List <ReadyToTransferPDU> responseList = new List <ReadyToTransferPDU>(); commandsToExecute = new List <SCSICommandPDU>(); ISCSISession session = connection.Session; if (command.Write && command.DataSegmentLength < command.ExpectedDataTransferLength) { uint transferTag = session.GetNextTransferTag(); // Create buffer for next segments (we only execute the command after receiving all of its data) Array.Resize <byte>(ref command.Data, (int)command.ExpectedDataTransferLength); // Send R2Ts: uint bytesLeft = command.ExpectedDataTransferLength - command.DataSegmentLength; uint nextOffset = command.DataSegmentLength; if (!session.InitialR2T) { uint firstDataPDULength = Math.Min((uint)session.FirstBurstLength, command.ExpectedDataTransferLength) - command.DataSegmentLength; bytesLeft -= firstDataPDULength; nextOffset += firstDataPDULength; } int totalR2Ts = (int)Math.Ceiling((double)bytesLeft / connection.TargetMaxRecvDataSegmentLength); int outgoingR2Ts = Math.Min(session.MaxOutstandingR2T, totalR2Ts); for (uint index = 0; index < outgoingR2Ts; index++) { ReadyToTransferPDU response = new ReadyToTransferPDU(); response.InitiatorTaskTag = command.InitiatorTaskTag; response.R2TSN = index; // R2Ts are sequenced per command and must start with 0 for each new command; response.TargetTransferTag = transferTag; response.BufferOffset = nextOffset; response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, command.ExpectedDataTransferLength - response.BufferOffset); responseList.Add(response); nextOffset += (uint)connection.TargetMaxRecvDataSegmentLength; } connection.AddTransfer(transferTag, command, (uint)outgoingR2Ts, nextOffset, (uint)totalR2Ts); session.CommandsInTransfer.Add(command.CmdSN); return(responseList); } if (session.IsPrecedingCommandPending(command.CmdSN)) { session.DelayedCommands.Add(command); } else { commandsToExecute.Add(command); } return(responseList); }
public ISCSISession StartSession(string initiatorName, ulong isid) { ushort tsih = GetNextTSIH(); ISCSISession session = new ISCSISession(initiatorName, isid, tsih); lock (m_activeSessions) { m_activeSessions.Add(session); } return(session); }
public ConnectionState FindConnection(ISCSISession session, ushort cid) { lock (m_activeConnections) { int index = GetConnectionStateIndex(session, cid); if (index >= 0) { return(m_activeConnections[index]); } return(null); } }
private int GetConnectionStateIndex(ISCSISession session, ushort cid) { for (int index = 0; index < m_activeConnections.Count; index++) { if (String.Equals(m_activeConnections[index].Session.InitiatorName, session.InitiatorName, StringComparison.OrdinalIgnoreCase) && m_activeConnections[index].Session.ISID == session.ISID && m_activeConnections[index].Session.TSIH == session.TSIH && m_activeConnections[index].ConnectionParameters.CID == cid) { return(index); } } return(-1); }
public void RemoveSession(ISCSISession session, SessionTerminationReason reason) { lock (m_activeSessions) { int index = GetSessionIndex(session.InitiatorName, session.ISID, session.TSIH); if (index >= 0) { ISCSITarget target = m_activeSessions[index].Target; if (target != null) { target.NotifySessionTermination(session.InitiatorName, session.ISID, reason); } m_activeSessions.RemoveAt(index); } } }
public ISCSISession FindSession(string initiatorName, ulong isid, string targetName) { lock (m_activeSessions) { for (int index = 0; index < m_activeSessions.Count; index++) { ISCSISession session = m_activeSessions[index]; if (String.Equals(session.InitiatorName, initiatorName, StringComparison.OrdinalIgnoreCase) && session.ISID == isid && session.Target != null && String.Equals(session.Target.TargetName, targetName, StringComparison.OrdinalIgnoreCase)) { return(session); } } } return(null); }
public List <ConnectionState> GetSessionConnections(ISCSISession session) { List <ConnectionState> result = new List <ConnectionState>(); lock (m_activeConnections) { for (int index = 0; index < m_activeConnections.Count; index++) { if (String.Equals(m_activeConnections[index].Session.InitiatorName, session.InitiatorName, StringComparison.OrdinalIgnoreCase) && m_activeConnections[index].Session.ISID == session.ISID && m_activeConnections[index].Session.TSIH == session.TSIH) { result.Add(m_activeConnections[index]); } } } return(result); }
private LoginResponseStatusName SetUpNormalSession(LoginRequestPDU request, string targetName, ConnectionParameters connection) { ISCSISession session = connection.Session; session.IsDiscovery = false; // If there's an existing session between this initiator and the target, we should terminate the // old session before reinstating a new iSCSI session in its place. ISCSISession existingSession = m_sessionManager.FindSession(session.InitiatorName, request.ISID, targetName); if (existingSession != null) { Log(Severity.Verbose, "[{0}] Terminating old session with target: {1}", connection.ConnectionIdentifier, targetName); List <ConnectionState> existingConnections = m_connectionManager.GetSessionConnections(existingSession); foreach (ConnectionState existingConnection in existingConnections) { m_connectionManager.ReleaseConnection(existingConnection); } m_sessionManager.RemoveSession(existingSession, SessionTerminationReason.ImplicitLogout); } // We use m_targets.Lock to synchronize between the login logic and the target removal logic. lock (m_targets.Lock) { ISCSITarget target = m_targets.FindTarget(targetName); if (target != null) { if (!target.AuthorizeInitiator(session.InitiatorName, session.ISID, connection.InitiatorEndPoint)) { Log(Severity.Warning, "[{0}] Initiator was not authorized to access {1}", connection.ConnectionIdentifier, targetName); return(LoginResponseStatusName.AuthorizationFailure); } session.Target = target; } else { Log(Severity.Warning, "[{0}] Initiator requested an unknown target: {1}", connection.ConnectionIdentifier, targetName); return(LoginResponseStatusName.NotFound); } } return(LoginResponseStatusName.Success); }
internal static List <ReadyToTransferPDU> GetReadyToTransferPDUs(SCSIDataOutPDU request, ConnectionParameters connection, out List <SCSICommandPDU> commandsToExecute) { List <ReadyToTransferPDU> responseList = new List <ReadyToTransferPDU>(); commandsToExecute = new List <SCSICommandPDU>(); ISCSISession session = connection.Session; TransferEntry transfer = connection.GetTransferEntry(request.TargetTransferTag); if (transfer == null) { throw new InvalidTargetTransferTagException(request.TargetTransferTag); } uint offset = request.BufferOffset; uint totalLength = (uint)transfer.Command.ExpectedDataTransferLength; // Store segment (we only execute the command after receiving all of its data) Array.Copy(request.Data, 0, transfer.Command.Data, offset, request.DataSegmentLength); if (offset + request.DataSegmentLength == totalLength) { // Last Data-out PDU connection.RemoveTransfer(request.TargetTransferTag); session.CommandsInTransfer.Remove(transfer.Command.CmdSN); if (session.IsPrecedingCommandPending(transfer.Command.CmdSN)) { session.DelayedCommands.Add(transfer.Command); } else { commandsToExecute.Add(transfer.Command); // Check if delayed commands are ready to be executed List <SCSICommandPDU> pendingCommands = session.GetDelayedCommandsReadyForExecution(); foreach (SCSICommandPDU pendingCommand in pendingCommands) { commandsToExecute.Add(pendingCommand); } } return(responseList); } else { // RFC 3720: An R2T MAY be answered with one or more SCSI Data-Out PDUs with a matching Target Transfer Tag. // If an R2T is answered with a single Data-Out PDU, the Buffer Offset in the Data PDU MUST be the same as the one specified // by the R2T, and the data length of the Data PDU MUST be the same as the Desired Data Transfer Length specified in the R2T. // If the R2T is answered with a sequence of Data PDUs, the Buffer Offset and Length MUST be within // the range of those specified by R2T, and the last PDU MUST have the F bit set to 1. // An R2T is considered outstanding until the last data PDU is transferred. if (request.Final) { // We already sent as many R2T as we could, we will only send R2T if any remained. if (transfer.NextR2TSN < transfer.TotalR2Ts) { // Send R2T ReadyToTransferPDU response = new ReadyToTransferPDU(); response.InitiatorTaskTag = request.InitiatorTaskTag; response.TargetTransferTag = request.TargetTransferTag; response.R2TSN = transfer.NextR2TSN; response.BufferOffset = transfer.NextOffset; // where we left off response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, totalLength - response.BufferOffset); responseList.Add(response); transfer.NextR2TSN++; transfer.NextOffset += (uint)connection.TargetMaxRecvDataSegmentLength; } } return(responseList); } }
private LoginResponseStatusName SetUpSession(LoginRequestPDU request, KeyValuePairList <string, string> requestParameters, ConnectionParameters connection) { string initiatorName = requestParameters.ValueOf("InitiatorName"); if (String.IsNullOrEmpty(initiatorName)) { // RFC 3720: InitiatorName: The initiator of the TCP connection MUST provide this key [..] // at the first Login of the Login Phase for every connection. string loginIdentifier = String.Format("ISID={0},TSIH={1},CID={2}", request.ISID.ToString("x"), request.TSIH.ToString("x"), request.CID.ToString("x")); Log(Severity.Warning, "[{0}] Initiator error: InitiatorName was not included in the login request", loginIdentifier); return(LoginResponseStatusName.InitiatorError); } if (request.TSIH == 0) { // Note: An initiator could login with the same ISID to a different target (another session), // We should only perform session reinstatement when an initiator is logging in to the same target. // For a new session, the request TSIH is zero, // As part of the response, the target generates a TSIH. connection.Session = m_sessionManager.StartSession(initiatorName, request.ISID); connection.CID = request.CID; Log(Severity.Verbose, "[{0}] Session has been started", connection.Session.SessionIdentifier); connection.Session.CommandNumberingStarted = true; connection.Session.ExpCmdSN = request.CmdSN; string sessionType = requestParameters.ValueOf("SessionType"); if (sessionType == "Discovery") { connection.Session.IsDiscovery = true; } else //sessionType == "Normal" or unspecified (default is Normal) { if (requestParameters.ContainsKey("TargetName")) { string targetName = requestParameters.ValueOf("TargetName"); return(SetUpNormalSession(request, targetName, connection)); } else { // RFC 3720: For any connection within a session whose type is not "Discovery", the first Login Request MUST also include the TargetName key=value pair. Log(Severity.Warning, "[{0}] Initiator error: TargetName was not included in a non-discovery session", connection.ConnectionIdentifier); return(LoginResponseStatusName.InitiatorError); } } } else { ISCSISession existingSession = m_sessionManager.FindSession(initiatorName, request.ISID, request.TSIH); if (existingSession == null) { return(LoginResponseStatusName.SessionDoesNotExist); } else { connection.Session = existingSession; ConnectionState existingConnection = m_connectionManager.FindConnection(existingSession, request.CID); if (existingConnection != null) { // do connection reinstatement Log(Severity.Verbose, "[{0}] Initiating implicit logout", existingConnection.ConnectionIdentifier); m_connectionManager.ReleaseConnection(existingConnection); } else { // add a new connection to the session if (m_connectionManager.GetSessionConnections(existingSession).Count > existingSession.MaxConnections) { return(LoginResponseStatusName.TooManyConnections); } } connection.CID = request.CID; } } return(LoginResponseStatusName.Success); }
private static void UpdateOperationalParameters(KeyValuePairList <string, string> loginParameters, ConnectionParameters connection) { ISCSISession session = connection.Session; string value = loginParameters.ValueOf("MaxRecvDataSegmentLength"); if (value != null) { connection.InitiatorMaxRecvDataSegmentLength = Convert.ToInt32(value); } value = loginParameters.ValueOf("MaxConnections"); if (value != null) { session.MaxConnections = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.MaxConnections); } value = loginParameters.ValueOf("InitialR2T"); if (value != null) { session.InitialR2T = (value == "Yes") || ISCSIServer.DesiredParameters.InitialR2T; } value = loginParameters.ValueOf("ImmediateData"); if (value != null) { session.ImmediateData = (value == "Yes") && ISCSIServer.DesiredParameters.ImmediateData; } value = loginParameters.ValueOf("MaxBurstLength"); if (value != null) { session.MaxBurstLength = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.MaxBurstLength); } value = loginParameters.ValueOf("FirstBurstLength"); if (value != null) { session.FirstBurstLength = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.FirstBurstLength); } value = loginParameters.ValueOf("DataPDUInOrder"); if (value != null) { session.DataPDUInOrder = (value == "Yes") || ISCSIServer.DesiredParameters.DataPDUInOrder; } value = loginParameters.ValueOf("DataSequenceInOrder"); if (value != null) { session.DataSequenceInOrder = (value == "Yes") || ISCSIServer.DesiredParameters.DataSequenceInOrder; } value = loginParameters.ValueOf("DefaultTime2Wait"); if (value != null) { session.DefaultTime2Wait = Math.Max(Convert.ToInt32(value), ISCSIServer.DesiredParameters.DefaultTime2Wait); } value = loginParameters.ValueOf("DefaultTime2Retain"); if (value != null) { session.DefaultTime2Retain = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.DefaultTime2Retain); } value = loginParameters.ValueOf("MaxOutstandingR2T"); if (value != null) { session.MaxOutstandingR2T = Math.Min(Convert.ToInt32(value), ISCSIServer.DesiredParameters.MaxOutstandingR2T); } }
private LoginResponsePDU GetFinalLoginResponsePDU(LoginRequestPDU request, KeyValuePairList <string, string> requestParameters, ConnectionParameters connection) { LoginResponsePDU response = GetLoginResponseTemplate(request); response.Transit = request.Transit; response.TSIH = connection.Session.TSIH; string connectionIdentifier = connection.ConnectionIdentifier; response.Status = LoginResponseStatusName.Success; ISCSISession session = connection.Session; // RFC 3720: The login process proceeds in two stages - the security negotiation // stage and the operational parameter negotiation stage. Both stages are optional // but at least one of them has to be present. // The stage codes are: // 0 - SecurityNegotiation // 1 - LoginOperationalNegotiation // 3 - FullFeaturePhase if (request.CurrentStage == 0) { KeyValuePairList <string, string> responseParameters = new KeyValuePairList <string, string>(); responseParameters.Add("AuthMethod", "None"); if (session.Target != null) { // RFC 3720: During the Login Phase the iSCSI target MUST return the TargetPortalGroupTag key with the first Login Response PDU with which it is allowed to do so responseParameters.Add("TargetPortalGroupTag", "1"); } if (request.Transit) { if (request.NextStage == 3) { session.IsFullFeaturePhase = true; } else if (request.NextStage != 1) { Log(Severity.Warning, "[{0}] Initiator error: Received login request with Invalid NextStage", connectionIdentifier); response.Status = LoginResponseStatusName.InitiatorError; } } response.LoginParameters = responseParameters; } else if (request.CurrentStage == 1) { UpdateOperationalParameters(requestParameters, connection); response.LoginParameters = GetLoginResponseOperationalParameters(connection); if (request.Transit) { if (request.NextStage == 3) { session.IsFullFeaturePhase = true; } else { Log(Severity.Warning, "[{0}] Initiator error: Received login request with Invalid NextStage", connectionIdentifier); response.Status = LoginResponseStatusName.InitiatorError; } } } else { // Not valid Log(Severity.Warning, "[{0}] Initiator error: Received login request with Invalid CurrentStage", connectionIdentifier); response.Status = LoginResponseStatusName.InitiatorError; } return(response); }