Beispiel #1
0
        /// <summary>
        /// Parses a simple chunk and does not attempt to process any chunk value.
        /// This method is suitable when:
        ///  - the chunk type consists only of the 4 byte header and has
        ///    no fixed or variable parameters set.
        /// </summary>
        /// <param name="buffer">The buffer holding the serialised chunk.</param>
        /// <param name="posn">The position to start parsing at.</param>
        /// <returns>An SCTP chunk instance.</returns>
        public static SctpChunk ParseBaseChunk(byte[] buffer, int posn)
        {
            var    chunk       = new SctpChunk();
            ushort chunkLength = chunk.ParseFirstWord(buffer, posn);

            if (chunkLength > SCTP_CHUNK_HEADER_LENGTH)
            {
                chunk.ChunkValue = new byte[chunkLength - SCTP_CHUNK_HEADER_LENGTH];
                Buffer.BlockCopy(buffer, posn + SCTP_CHUNK_HEADER_LENGTH, chunk.ChunkValue, 0, chunk.ChunkValue.Length);
            }

            return(chunk);
        }
Beispiel #2
0
        /// <summary>
        /// Parses the chunks from a serialised SCTP packet.
        /// </summary>
        /// <param name="buffer">The buffer holding the serialised packet.</param>
        /// <param name="offset">The position in the buffer of the packet.</param>
        /// <param name="length">The length of the serialised packet in the buffer.</param>
        /// <returns>The lsit of parsed chunks and a list of unrecognised chunks that were not de-serialised.</returns>
        private static (List <SctpChunk> chunks, List <byte[]> unrecognisedChunks) ParseChunks(byte[] buffer, int offset, int length)
        {
            List <SctpChunk> chunks             = new List <SctpChunk>();
            List <byte[]>    unrecognisedChunks = new List <byte[]>();

            int posn = offset + SctpHeader.SCTP_HEADER_LENGTH;

            bool stop = false;

            while (posn < length)
            {
                byte chunkType = buffer[posn];

                if (Enum.IsDefined(typeof(SctpChunkType), chunkType))
                {
                    var chunk = SctpChunk.Parse(buffer, posn);
                    chunks.Add(chunk);
                }
                else
                {
                    switch (SctpChunk.GetUnrecognisedChunkAction(chunkType))
                    {
                    case SctpUnrecognisedChunkActions.Stop:
                        stop = true;
                        break;

                    case SctpUnrecognisedChunkActions.StopAndReport:
                        stop = true;
                        unrecognisedChunks.Add(SctpChunk.CopyUnrecognisedChunk(buffer, posn));
                        break;

                    case SctpUnrecognisedChunkActions.Skip:
                        break;

                    case SctpUnrecognisedChunkActions.SkipAndReport:
                        unrecognisedChunks.Add(SctpChunk.CopyUnrecognisedChunk(buffer, posn));
                        break;
                    }
                }

                if (stop)
                {
                    logger.LogWarning($"SCTP unrecognised chunk type {chunkType} indicated no further chunks should be processed.");
                    break;
                }

                posn += (int)SctpChunk.GetChunkLengthFromHeader(buffer, posn, true);
            }

            return(chunks, unrecognisedChunks);
        }
Beispiel #3
0
        /// <summary>
        /// Sends a SCTP chunk to the remote party.
        /// </summary>
        /// <param name="chunk">The chunk to send.</param>
        internal void SendChunk(SctpChunk chunk)
        {
            if (!_wasAborted)
            {
                SctpPacket pkt = new SctpPacket(
                    _sctpSourcePort,
                    _sctpDestinationPort,
                    _remoteVerificationTag);

                pkt.AddChunk(chunk);

                byte[] buffer = pkt.GetBytes();

                _sctpTransport.Send(ID, buffer, 0, buffer.Length);
            }
        }
Beispiel #4
0
        /// <summary>
        /// Initialises the association state based on the echoed cookie (the cookie that we sent
        /// to the remote party and was then echoed back to us). An association can only be initialised
        /// from a cookie prior to it being used and prior to it ever having entered the established state.
        /// </summary>
        /// <param name="cookie">The echoed cookie that was returned from the remote party.</param>
        public void GotCookie(SctpTransportCookie cookie)
        {
            // The CookieEchoed state is allowed, even though a cookie should be creating a brand
            // new association rather than one that has already sent an INIT, in order to deal with
            // a race condition where both SCTP end points attempt to establish the association at
            // the same time using the same ports.
            if (_wasAborted || _wasShutdown)
            {
                logger.LogWarning($"SCTP association cannot initialise with a cookie after an abort or shutdown.");
            }
            else if (!(State == SctpAssociationState.Closed || State == SctpAssociationState.CookieEchoed))
            {
                throw new ApplicationException($"SCTP association cannot initialise with a cookie in state {State}.");
            }
            else
            {
                _sctpSourcePort      = cookie.SourcePort;
                _sctpDestinationPort = cookie.DestinationPort;
                VerificationTag      = cookie.Tag;
                ARwnd       = cookie.ARwnd;
                Destination = !string.IsNullOrEmpty(cookie.RemoteEndPoint) ?
                              IPSocket.Parse(cookie.RemoteEndPoint) : null;

                if (_dataReceiver == null)
                {
                    _dataReceiver = new SctpDataReceiver(ARwnd, _defaultMTU, cookie.RemoteTSN);
                }

                if (_dataSender == null)
                {
                    _dataSender = new SctpDataSender(ID, this.SendChunk, _defaultMTU, cookie.TSN, cookie.RemoteARwnd);
                }

                InitRemoteProperties(cookie.RemoteTag, cookie.RemoteTSN, cookie.RemoteARwnd);

                var cookieAckChunk = new SctpChunk(SctpChunkType.COOKIE_ACK);
                SendChunk(cookieAckChunk);

                SetState(SctpAssociationState.Established);
                _dataSender.StartSending();
                CancelTimers();
            }
        }
Beispiel #5
0
        /// <summary>
        /// Implements the SCTP association state machine.
        /// </summary>
        /// <param name="packet">An SCTP packet received from the remote party.</param>
        /// <remarks>
        /// SCTP Association State Diagram:
        /// https://tools.ietf.org/html/rfc4960#section-4
        /// </remarks>
        internal void OnPacketReceived(SctpPacket packet)
        {
            if (_wasAborted)
            {
                logger.LogWarning($"SCTP packet received but association has been aborted, ignoring.");
            }
            else if (packet.Header.VerificationTag != VerificationTag)
            {
                logger.LogWarning($"SCTP packet dropped due to wrong verification tag, expected " +
                                  $"{VerificationTag} got {packet.Header.VerificationTag}.");
            }
            else if (!_sctpTransport.IsPortAgnostic && packet.Header.DestinationPort != _sctpSourcePort)
            {
                logger.LogWarning($"SCTP packet dropped due to wrong SCTP destination port, expected " +
                                  $"{_sctpSourcePort} got {packet.Header.DestinationPort}.");
            }
            else if (!_sctpTransport.IsPortAgnostic && packet.Header.SourcePort != _sctpDestinationPort)
            {
                logger.LogWarning($"SCTP packet dropped due to wrong SCTP source port, expected " +
                                  $"{_sctpDestinationPort} got {packet.Header.SourcePort}.");
            }
            else
            {
                foreach (var chunk in packet.Chunks)
                {
                    var chunkType = (SctpChunkType)chunk.ChunkType;

                    switch (chunkType)
                    {
                    case SctpChunkType.ABORT:
                        string abortReason = (chunk as SctpAbortChunk).GetAbortReason();
                        logger.LogWarning($"SCTP packet ABORT chunk received from remote party, reason {abortReason}.");
                        _wasAborted = true;
                        OnAbortReceived?.Invoke(abortReason);
                        break;

                    case var ct when ct == SctpChunkType.COOKIE_ACK && State != SctpAssociationState.CookieEchoed:
                        // https://tools.ietf.org/html/rfc4960#section-5.2.5
                        // At any state other than COOKIE-ECHOED, an endpoint should silently
                        // discard a received COOKIE ACK chunk.
                        break;

                    case var ct when ct == SctpChunkType.COOKIE_ACK && State == SctpAssociationState.CookieEchoed:
                        SetState(SctpAssociationState.Established);
                        CancelTimers();
                        _dataSender.StartSending();
                        break;

                    case SctpChunkType.COOKIE_ECHO:
                        // In standard operation an SCTP association gets created when the parent transport
                        // receives a COOKIE ECHO chunk. The association gets initialised from the chunk and
                        // does not need to process it.
                        // The scenarios in https://tools.ietf.org/html/rfc4960#section-5.2 describe where
                        // an association could receive a COOKIE ECHO.
                        break;

                    case SctpChunkType.DATA:
                        var dataChunk = chunk as SctpDataChunk;

                        if (dataChunk.UserData == null || dataChunk.UserData.Length == 0)
                        {
                            // Fatal condition:
                            // - If an endpoint receives a DATA chunk with no user data (i.e., the
                            //   Length field is set to 16), it MUST send an ABORT with error cause
                            //   set to "No User Data". (RFC4960 pg. 80)
                            Abort(new SctpErrorNoUserData {
                                TSN = (chunk as SctpDataChunk).TSN
                            });
                        }
                        else
                        {
                            logger.LogTrace($"SCTP data chunk received on ID {ID} with TSN {dataChunk.TSN}, payload length {dataChunk.UserData.Length}, flags {dataChunk.ChunkFlags:X2}.");

                            // A received data chunk can result in multiple data frames becoming available.
                            // For example if a stream has out of order frames already received and the next
                            // in order frame arrives then all the in order ones will be supplied.
                            var sortedFrames = _dataReceiver.OnDataChunk(dataChunk);

                            var sack = _dataReceiver.GetSackChunk();
                            if (sack != null)
                            {
                                SendChunk(sack);
                            }

                            foreach (var frame in sortedFrames)
                            {
                                OnData?.Invoke(frame);
                            }
                        }

                        break;

                    case SctpChunkType.ERROR:
                        var errorChunk = chunk as SctpErrorChunk;
                        foreach (var err in errorChunk.ErrorCauses)
                        {
                            logger.LogWarning($"SCTP error {err.CauseCode}.");
                        }
                        break;

                    case SctpChunkType.HEARTBEAT:
                        // The HEARTBEAT ACK sends back the same chunk but with the type changed.
                        chunk.ChunkType = (byte)SctpChunkType.HEARTBEAT_ACK;
                        SendChunk(chunk);
                        break;

                    case var ct when ct == SctpChunkType.INIT_ACK && State != SctpAssociationState.CookieWait:
                        // https://tools.ietf.org/html/rfc4960#section-5.2.3
                        // If an INIT ACK is received by an endpoint in any state other than the
                        // COOKIE - WAIT state, the endpoint should discard the INIT ACK chunk.
                        break;

                    case var ct when ct == SctpChunkType.INIT_ACK && State == SctpAssociationState.CookieWait:

                        if (_t1Init != null)
                        {
                            _t1Init.Dispose();
                            _t1Init = null;
                        }

                        var initAckChunk = chunk as SctpInitChunk;

                        if (initAckChunk.InitiateTag == 0 ||
                            initAckChunk.NumberInboundStreams == 0 ||
                            initAckChunk.NumberOutboundStreams == 0)
                        {
                            // Fatal conditions:
                            //  - The Initiate Tag MUST NOT take the value 0. (RFC4960 pg 30).
                            //  - Note: A receiver of an INIT ACK with the OS value set to 0 SHOULD
                            //    destroy the association discarding its TCB. (RFC4960 pg 31).
                            //  - Note: A receiver of an INIT ACK with the MIS value set to 0 SHOULD
                            //    destroy the association discarding its TCB. (RFC4960 pg 31).
                            Abort(new SctpCauseOnlyError(SctpErrorCauseCode.InvalidMandatoryParameter));
                        }
                        else
                        {
                            InitRemoteProperties(initAckChunk.InitiateTag, initAckChunk.InitialTSN, initAckChunk.ARwnd);

                            var cookie = initAckChunk.StateCookie;

                            // The cookie chunk parameter can be changed to a COOKE ECHO CHUNK by changing the first two bytes.
                            // But it's more convenient to create a new chunk.
                            var cookieEchoChunk = new SctpChunk(SctpChunkType.COOKIE_ECHO)
                            {
                                ChunkValue = cookie
                            };
                            var cookieEchoPkt = GetControlPacket(cookieEchoChunk);

                            if (initAckChunk.UnrecognizedPeerParameters.Count > 0)
                            {
                                var errChunk = new SctpErrorChunk();

                                foreach (var unrecognised in initAckChunk.UnrecognizedPeerParameters)
                                {
                                    var unrecognisedParams = new SctpErrorUnrecognizedParameters {
                                        UnrecognizedParameters = unrecognised.GetBytes()
                                    };
                                    errChunk.AddErrorCause(unrecognisedParams);
                                }

                                cookieEchoPkt.AddChunk(errChunk);
                            }

                            SendPacket(cookieEchoPkt);
                            SetState(SctpAssociationState.CookieEchoed);

                            _t1Cookie = new Timer(T1CookieTimerExpired, cookieEchoPkt, T1_COOKIE_TIMER_MILLISECONDS, T1_COOKIE_TIMER_MILLISECONDS);
                        }
                        break;

                    case var ct when ct == SctpChunkType.INIT_ACK && State != SctpAssociationState.CookieWait:
                        logger.LogWarning($"SCTP association received INIT_ACK chunk in wrong state of {State}, ignoring.");
                        break;

                    case SctpChunkType.SACK:
                        _dataSender.GotSack(chunk as SctpSackChunk);
                        break;

                    case var ct when ct == SctpChunkType.SHUTDOWN && State == SctpAssociationState.Established:
                        // TODO: Check outstanding data chunks.
                        var shutdownAck = new SctpChunk(SctpChunkType.SHUTDOWN_ACK);
                        SendChunk(shutdownAck);
                        SetState(SctpAssociationState.ShutdownAckSent);
                        break;

                    case var ct when ct == SctpChunkType.SHUTDOWN_ACK && State == SctpAssociationState.ShutdownSent:
                        SetState(SctpAssociationState.Closed);
                        var shutCompleteChunk = new SctpChunk(SctpChunkType.SHUTDOWN_COMPLETE,
                                                              (byte)(_remoteVerificationTag != 0 ? SHUTDOWN_CHUNK_TBIT_FLAG : 0x00));
                        var shutCompletePkt = GetControlPacket(shutCompleteChunk);
                        shutCompletePkt.Header.VerificationTag = packet.Header.VerificationTag;
                        SendPacket(shutCompletePkt);
                        break;

                    case var ct when ct == SctpChunkType.SHUTDOWN_COMPLETE &&
                        (State == SctpAssociationState.ShutdownAckSent || State == SctpAssociationState.ShutdownSent):
                        _wasShutdown = true;
                        SetState(SctpAssociationState.Closed);
                        break;

                    default:
                        logger.LogWarning($"SCTP association no rule for {chunkType} in state of {State}.");
                        break;
                    }
                }
            }
        }
Beispiel #6
0
 /// <summary>
 /// Adds a new chunk to send with an outgoing packet.
 /// </summary>
 /// <param name="chunk">The chunk to add.</param>
 public void AddChunk(SctpChunk chunk)
 {
     Chunks.Add(chunk);
 }
Beispiel #7
0
 /// <summary>
 /// Copies an unrecognised chunk to a byte buffer and returns it. This method is
 /// used to assist in reporting unrecognised chunk types.
 /// </summary>
 /// <param name="buffer">The buffer containing the chunk.</param>
 /// <param name="posn">The position in the buffer that the unrecognised chunk starts.</param>
 /// <returns>A new buffer containing a copy of the chunk.</returns>
 public static byte[] CopyUnrecognisedChunk(byte[] buffer, int posn)
 {
     byte[] unrecognised = new byte[SctpChunk.GetChunkLengthFromHeader(buffer, posn, true)];
     Buffer.BlockCopy(buffer, posn, unrecognised, 0, unrecognised.Length);
     return(unrecognised);
 }