/// <summary> /// Create a new SCTP association instance where the INIT will be generated /// from this end of the connection. /// </summary> /// <param name="sctpTransport">The transport layer doing the actual sending and receiving of /// packets, e.g. UDP, DTLS, raw sockets etc.</param> /// <param name="destination">Optional. The remote destination end point for this association. /// Some transports, such as DTLS, are already established and do not use this parameter.</param> /// <param name="sctpSourcePort">The source port for the SCTP packet header.</param> /// <param name="sctpDestinationPort">The destination port for the SCTP packet header.</param> /// <param name="defaultMTU">The default Maximum Transmission Unit (MTU) for the underlying /// transport. This determines the maximum size of an SCTP packet that will be used with /// the transport.</param> /// <param name="localTransportPort">Optional. The local transport (e.g. UDP or DTLS) port being /// used for the underlying SCTP transport. This be set on the SCTP association's ID to aid in /// diagnostics.</param> public SctpAssociation( SctpTransport sctpTransport, IPEndPoint destination, ushort sctpSourcePort, ushort sctpDestinationPort, ushort defaultMTU, int localTransportPort, ushort numberOutboundStreams = DEFAULT_NUMBER_OUTBOUND_STREAMS, ushort numberInboundStreams = DEFAULT_NUMBER_INBOUND_STREAMS) { _sctpTransport = sctpTransport; Destination = destination; _sctpSourcePort = sctpSourcePort; _sctpDestinationPort = sctpDestinationPort; _defaultMTU = defaultMTU; _numberOutboundStreams = numberOutboundStreams; _numberInboundStreams = numberInboundStreams; VerificationTag = Crypto.GetRandomUInt(true); ID = $"{sctpSourcePort}:{sctpDestinationPort}:{localTransportPort}"; ARwnd = DEFAULT_ADVERTISED_RECEIVE_WINDOW; _dataReceiver = new SctpDataReceiver(ARwnd, _defaultMTU, 0); _dataSender = new SctpDataSender(ID, this.SendChunk, defaultMTU, Crypto.GetRandomUInt(true), DEFAULT_ADVERTISED_RECEIVE_WINDOW); State = SctpAssociationState.Closed; }
/// <summary> /// Updates the sender state for the gap reports received in a SACH chunk from the /// remote peer. /// </summary> /// <param name="sackGapBlocks">The gap reports from the remote peer.</param> /// <param name="maxTSNDistance">The maximum distance any valid TSN should be from the current /// ACK'ed TSN. If this distance gets exceeded by a gap report then it's likely something has been /// miscalculated.</param> private void ProcessGapReports(List <SctpTsnGapBlock> sackGapBlocks, uint maxTSNDistance) { uint lastAckTSN = _cumulativeAckTSN; foreach (var gapBlock in sackGapBlocks) { uint goodTSNStart = _cumulativeAckTSN + gapBlock.Start; if (SctpDataReceiver.GetDistance(lastAckTSN, goodTSNStart) > maxTSNDistance) { logger.LogWarning($"SCTP SACK gap report had a start TSN of {goodTSNStart} too distant from last good TSN {lastAckTSN}, ignoring rest of SACK."); break; } else if (!SctpDataReceiver.IsNewer(lastAckTSN, goodTSNStart)) { logger.LogWarning($"SCTP SACK gap report had a start TSN of {goodTSNStart} behind last good TSN {lastAckTSN}, ignoring rest of SACK."); break; } else { uint missingTSN = lastAckTSN + 1; logger.LogTrace($"SCTP SACK gap report start TSN {goodTSNStart} gap report end TSN {_cumulativeAckTSN + gapBlock.End} " + $"first missing TSN {missingTSN}."); while (missingTSN != goodTSNStart) { if (!_missingChunks.ContainsKey(missingTSN)) { if (!_unconfirmedChunks.ContainsKey(missingTSN)) { // What to do? Can't retransmit a chunk that's no longer available. // Hope it's a transient error from a duplicate or out of order SACK. // TODO: Maybe keep count of how many time this occurs and send an ABORT if it // gets to a certain threshold. logger.LogWarning($"SCTP SACK gap report reported missing TSN of {missingTSN} but no matching unconfirmed chunk available."); break; } else { logger.LogTrace($"SCTP SACK gap adding retransmit entry for TSN {missingTSN}."); _missingChunks.TryAdd(missingTSN, 0); } } missingTSN++; } } lastAckTSN = _cumulativeAckTSN + gapBlock.End; } }
/// <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(); } }
/// <summary> /// Handler for SACK chunks received from the remote peer. /// </summary> /// <param name="sack">The SACK chunk from the remote peer.</param> public void GotSack(SctpSackChunk sack) { if (sack != null) { _inRetransmitMode = false; unchecked { uint maxTSNDistance = SctpDataReceiver.GetDistance(_cumulativeAckTSN, TSN); bool processGapReports = true; if (_unconfirmedChunks.TryGetValue(sack.CumulativeTsnAck, out var result)) { _lastAckedDataChunkSize = result.UserData.Length; } if (!_gotFirstSACK) { if (SctpDataReceiver.GetDistance(_initialTSN, sack.CumulativeTsnAck) < maxTSNDistance && SctpDataReceiver.IsNewerOrEqual(_initialTSN, sack.CumulativeTsnAck)) { logger.LogTrace($"SCTP first SACK remote peer TSN ACK {sack.CumulativeTsnAck} next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.GapAckBlocks.Count})."); _gotFirstSACK = true; _cumulativeAckTSN = _initialTSN; RemoveAckedUnconfirmedChunks(sack.CumulativeTsnAck); } } else { if (_cumulativeAckTSN != sack.CumulativeTsnAck) { if (SctpDataReceiver.GetDistance(_cumulativeAckTSN, sack.CumulativeTsnAck) > maxTSNDistance) { logger.LogWarning($"SCTP SACK TSN from remote peer of {sack.CumulativeTsnAck} was too distant from the expected {_cumulativeAckTSN}, ignoring."); processGapReports = false; } else if (!SctpDataReceiver.IsNewer(_cumulativeAckTSN, sack.CumulativeTsnAck)) { logger.LogWarning($"SCTP SACK TSN from remote peer of {sack.CumulativeTsnAck} was behind expected {_cumulativeAckTSN}, ignoring."); processGapReports = false; } else { logger.LogTrace($"SCTP SACK remote peer TSN ACK {sack.CumulativeTsnAck}, next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.GapAckBlocks.Count})."); RemoveAckedUnconfirmedChunks(sack.CumulativeTsnAck); } } else { logger.LogTrace($"SCTP SACK remote peer TSN ACK no change {_cumulativeAckTSN}, next sender TSN {TSN}, arwnd {sack.ARwnd} (gap reports {sack.GapAckBlocks.Count})."); RemoveAckedUnconfirmedChunks(sack.CumulativeTsnAck); } } // Check gap reports. Only process them if the cumulative ACK TSN was acceptable. if (processGapReports && sack.GapAckBlocks.Count > 0) { ProcessGapReports(sack.GapAckBlocks, maxTSNDistance); } } _receiverWindow = CalculateReceiverWindow(sack.ARwnd); _congestionWindow = CalculateCongestionWindow(_lastAckedDataChunkSize); // SACK's will normally allow more data to be sent. _senderMre.Set(); } }