Beispiel #1
0
        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);
        }
Beispiel #2
0
        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);
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
 public ConnectionState FindConnection(ISCSISession session, ushort cid)
 {
     lock (m_activeConnections)
     {
         int index = GetConnectionStateIndex(session, cid);
         if (index >= 0)
         {
             return(m_activeConnections[index]);
         }
         return(null);
     }
 }
Beispiel #5
0
 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);
 }
Beispiel #6
0
 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);
         }
     }
 }
Beispiel #7
0
 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);
 }
Beispiel #8
0
        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);
        }
Beispiel #9
0
        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);
        }
Beispiel #10
0
        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);
            }
        }
Beispiel #11
0
        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);
        }
Beispiel #12
0
        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);
            }
        }
Beispiel #13
0
        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);
        }