protected void GotInit(SctpPacket initPacket, IPEndPoint remoteEndPoint) { // INIT packets have specific processing rules in order to prevent resource exhaustion. // See Section 5 of RFC 4960 https://tools.ietf.org/html/rfc4960#section-5 "Association Initialization". SctpInitChunk initChunk = initPacket.Chunks.Single(x => x.KnownType == SctpChunkType.INIT) as SctpInitChunk; if (initChunk.InitiateTag == 0 || initChunk.NumberInboundStreams == 0 || initChunk.NumberOutboundStreams == 0) { // If the value of the Initiate Tag in a received INIT chunk is found // to be 0, the receiver MUST treat it as an error and close the // association by transmitting an ABORT. (RFC4960 pg. 25) // Note: A receiver of an INIT with the OS value set to 0 SHOULD // abort the association. (RFC4960 pg. 25) // Note: A receiver of an INIT with the MIS value of 0 SHOULD abort // the association. (RFC4960 pg. 26) SendError( true, initPacket.Header.DestinationPort, initPacket.Header.SourcePort, initChunk.InitiateTag, new SctpCauseOnlyError(SctpErrorCauseCode.InvalidMandatoryParameter)); } else { var initAckPacket = GetInitAck(initPacket, remoteEndPoint); var buffer = initAckPacket.GetBytes(); Send(null, buffer, 0, buffer.Length); } }
/// <summary> /// Parses an SCTP chunk from a buffer. /// </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 Parse(byte[] buffer, int posn) { if (buffer.Length < posn + SCTP_CHUNK_HEADER_LENGTH) { throw new ApplicationException("Buffer did not contain the minimum of bytes for an SCTP chunk."); } byte chunkType = buffer[posn]; if (Enum.IsDefined(typeof(SctpChunkType), chunkType)) { switch ((SctpChunkType)chunkType) { case SctpChunkType.ABORT: return(SctpAbortChunk.ParseChunk(buffer, posn, true)); case SctpChunkType.DATA: return(SctpDataChunk.ParseChunk(buffer, posn)); case SctpChunkType.ERROR: return(SctpErrorChunk.ParseChunk(buffer, posn, false)); case SctpChunkType.SACK: return(SctpSackChunk.ParseChunk(buffer, posn)); case SctpChunkType.COOKIE_ACK: case SctpChunkType.COOKIE_ECHO: case SctpChunkType.HEARTBEAT: case SctpChunkType.HEARTBEAT_ACK: case SctpChunkType.SHUTDOWN_ACK: case SctpChunkType.SHUTDOWN_COMPLETE: return(ParseBaseChunk(buffer, posn)); case SctpChunkType.INIT: case SctpChunkType.INIT_ACK: return(SctpInitChunk.ParseChunk(buffer, posn)); case SctpChunkType.SHUTDOWN: return(SctpShutdownChunk.ParseChunk(buffer, posn)); default: logger.LogDebug($"TODO: Implement parsing logic for well known chunk type {(SctpChunkType)chunkType}."); return(ParseBaseChunk(buffer, posn)); } } // Shouldn't reach this point. The SCTP packet parsing logic checks if the chunk is // recognised before attempting to parse it. throw new ApplicationException($"SCTP chunk type of {chunkType} was not recognised."); }
/// <summary> /// Creates the INIT ACK chunk and packet to send as a response to an SCTP /// packet containing an INIT chunk. /// </summary> /// <param name="initPacket">The received packet containing the INIT chunk.</param> /// <param name="remoteEP">Optional. The remote IP end point the INIT packet was /// received on. For transports that don't use an IP transport directly this parameter /// can be set to null and it will not form part of the COOKIE ECHO checks.</param> /// <returns>An SCTP packet with a single INIT ACK chunk.</returns> protected SctpPacket GetInitAck(SctpPacket initPacket, IPEndPoint remoteEP) { SctpInitChunk initChunk = initPacket.Chunks.Single(x => x.KnownType == SctpChunkType.INIT) as SctpInitChunk; SctpPacket initAckPacket = new SctpPacket( initPacket.Header.DestinationPort, initPacket.Header.SourcePort, initChunk.InitiateTag); var cookie = GetInitAckCookie( initPacket.Header.DestinationPort, initPacket.Header.SourcePort, initChunk.InitiateTag, initChunk.InitialTSN, initChunk.ARwnd, remoteEP != null ? remoteEP.ToString() : string.Empty, (int)(initChunk.CookiePreservative / 1000)); var json = cookie.ToJson(); var jsonBuffer = Encoding.UTF8.GetBytes(json); using (HMACSHA256 hmac = new HMACSHA256(_hmacKey)) { var result = hmac.ComputeHash(jsonBuffer); cookie.HMAC = result.HexStr(); } var jsonWithHMAC = cookie.ToJson(); var jsonBufferWithHMAC = Encoding.UTF8.GetBytes(jsonWithHMAC); SctpInitChunk initAckChunk = new SctpInitChunk( SctpChunkType.INIT_ACK, cookie.Tag, cookie.TSN, cookie.ARwnd, SctpAssociation.DEFAULT_NUMBER_OUTBOUND_STREAMS, SctpAssociation.DEFAULT_NUMBER_INBOUND_STREAMS); initAckChunk.StateCookie = jsonBufferWithHMAC; initAckChunk.UnrecognizedPeerParameters = initChunk.UnrecognizedPeerParameters; initAckPacket.AddChunk(initAckChunk); return(initAckPacket); }
/// <summary> /// Attempts to create an association with a remote party by sending an initialisation /// control chunk. /// </summary> private void SendInit() { if (!_wasAborted) { // A packet containing an INIT chunk MUST have a zero Verification Tag (RFC4960 Pg 15). SctpPacket init = new SctpPacket(_sctpSourcePort, _sctpDestinationPort, 0); SctpInitChunk initChunk = new SctpInitChunk( SctpChunkType.INIT, VerificationTag, TSN, ARwnd, _numberOutboundStreams, _numberInboundStreams); init.AddChunk(initChunk); SetState(SctpAssociationState.CookieWait); byte[] buffer = init.GetBytes(); _sctpTransport.Send(ID, buffer, 0, buffer.Length); _t1Init = new Timer(T1InitTimerExpired, init, T1_INIT_TIMER_MILLISECONDS, T1_INIT_TIMER_MILLISECONDS); } }
/// <summary> /// Parses the INIT or INIT ACK chunk fields /// </summary> /// <param name="buffer">The buffer holding the serialised chunk.</param> /// <param name="posn">The position to start parsing at.</param> public static SctpInitChunk ParseChunk(byte[] buffer, int posn) { var initChunk = new SctpInitChunk(); ushort chunkLen = initChunk.ParseFirstWord(buffer, posn); int startPosn = posn + SCTP_CHUNK_HEADER_LENGTH; initChunk.InitiateTag = NetConvert.ParseUInt32(buffer, startPosn); initChunk.ARwnd = NetConvert.ParseUInt32(buffer, startPosn + 4); initChunk.NumberOutboundStreams = NetConvert.ParseUInt16(buffer, startPosn + 8); initChunk.NumberInboundStreams = NetConvert.ParseUInt16(buffer, startPosn + 10); initChunk.InitialTSN = NetConvert.ParseUInt32(buffer, startPosn + 12); int paramPosn = startPosn + FIXED_PARAMETERS_LENGTH; int paramsBufferLength = chunkLen - SCTP_CHUNK_HEADER_LENGTH - FIXED_PARAMETERS_LENGTH; if (paramPosn < paramsBufferLength) { bool stopProcessing = false; foreach (var varParam in GetParameters(buffer, paramPosn, paramsBufferLength)) { switch (varParam.ParameterType) { case (ushort)SctpInitChunkParameterType.IPv4Address: case (ushort)SctpInitChunkParameterType.IPv6Address: var address = new IPAddress(varParam.ParameterValue); initChunk.Addresses.Add(address); break; case (ushort)SctpInitChunkParameterType.CookiePreservative: initChunk.CookiePreservative = NetConvert.ParseUInt32(varParam.ParameterValue, 0); break; case (ushort)SctpInitChunkParameterType.HostNameAddress: initChunk.HostnameAddress = Encoding.UTF8.GetString(varParam.ParameterValue); break; case (ushort)SctpInitChunkParameterType.SupportedAddressTypes: for (int valPosn = 0; valPosn < varParam.ParameterValue.Length; valPosn += 2) { switch (NetConvert.ParseUInt16(varParam.ParameterValue, valPosn)) { case (ushort)SctpInitChunkParameterType.IPv4Address: initChunk.SupportedAddressTypes.Add(SctpInitChunkParameterType.IPv4Address); break; case (ushort)SctpInitChunkParameterType.IPv6Address: initChunk.SupportedAddressTypes.Add(SctpInitChunkParameterType.IPv6Address); break; case (ushort)SctpInitChunkParameterType.HostNameAddress: initChunk.SupportedAddressTypes.Add(SctpInitChunkParameterType.HostNameAddress); break; } } break; case (ushort)SctpInitChunkParameterType.EcnCapable: break; case (ushort)SctpInitChunkParameterType.StateCookie: // Used with INIT ACK chunks only. initChunk.StateCookie = varParam.ParameterValue; break; case (ushort)SctpInitChunkParameterType.UnrecognizedParameter: // Used with INIT ACK chunks only. This parameter is the remote peer returning // a list of parameters it did not understand in the INIT chunk. initChunk.UnrecognizedParameters.Add(varParam.ParameterValue); break; default: // Parameters are not recognised in an INIT or INIT ACK. initChunk.GotUnrecognisedParameter(varParam); break; } if (stopProcessing) { logger.LogWarning($"SCTP unrecognised parameter {varParam.ParameterType} for chunk type {initChunk.KnownType} " + "indicated no further chunks should be processed."); break; } } } return(initChunk); }