コード例 #1
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;
                    }
                }
            }
        }