public void TestMessageIntegrityAttributeForBindingRequest() { Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().Name); byte[] stunReq = new byte[] { 0x00, 0x01, 0x00, 0x60, 0x21, 0x12, 0xa4, 0x42, 0x69, 0x64, 0x38, 0x2b, 0x4c, 0x45, 0x44, 0x57, 0x4d, 0x31, 0x64, 0x30, 0x00, 0x06, 0x00, 0x21, 0x75, 0x4f, 0x35, 0x73, 0x69, 0x31, 0x75, 0x61, 0x37, 0x63, 0x59, 0x34, 0x74, 0x38, 0x4d, 0x4d, 0x3a, 0x4c, 0x77, 0x38, 0x2f, 0x30, 0x43, 0x31, 0x43, 0x72, 0x76, 0x68, 0x5a, 0x43, 0x31, 0x67, 0x62, 0x00, 0x00, 0x00, 0x80, 0x2a, 0x00, 0x08, 0xc0, 0x3d, 0xf5, 0x13, 0x40, 0xf4, 0x22, 0x46, 0x00, 0x25, 0x00, 0x00, 0x00, 0x24, 0x00, 0x04, 0x6e, 0x7f, 0x1e, 0xff, 0x00, 0x08, 0x00, 0x14, 0x55, 0x82, 0x69, 0xde, 0x17, 0x55, 0xcc, 0x66, 0x29, 0x23, 0xe6, 0x7d, 0xec, 0x87, 0x6c, 0x07, 0x3a, 0xd6, 0x78, 0x15, 0x80, 0x28, 0x00, 0x04, 0x1c, 0xae, 0x89, 0x2e }; STUNv2Message stunMessage = STUNv2Message.ParseSTUNMessage(stunReq, stunReq.Length); STUNv2Header stunHeader = stunMessage.Header; Console.WriteLine("Request type = " + stunHeader.MessageType + "."); Console.WriteLine("Length = " + stunHeader.MessageLength + "."); Console.WriteLine("Transaction ID = " + BitConverter.ToString(stunHeader.TransactionId) + "."); Assert.AreEqual(STUNv2MessageTypesEnum.BindingRequest, stunHeader.MessageType); Assert.AreEqual(96, stunHeader.MessageLength); Assert.AreEqual(6, stunMessage.Attributes.Count); Assert.AreEqual("69-64-38-2B-4C-45-44-57-4D-31-64-30", BitConverter.ToString(stunMessage.Header.TransactionId)); stunMessage.Attributes.Remove(stunMessage.Attributes.Where(x => x.AttributeType == STUNv2AttributeTypesEnum.MessageIntegrity).Single()); stunMessage.Attributes.Remove(stunMessage.Attributes.Where(x => x.AttributeType == STUNv2AttributeTypesEnum.FingerPrint).Single()); byte[] buffer = stunMessage.ToByteBufferStringKey("r89XhWC9k2kW4Pns75vmwHIa", true); Assert.AreEqual(BitConverter.ToString(stunReq), BitConverter.ToString(buffer)); }
public void GenerateHmacAndFingerprintTestMethod() { logger.LogDebug("--> " + System.Reflection.MethodBase.GetCurrentMethod().Name); logger.BeginScope(System.Reflection.MethodBase.GetCurrentMethod().Name); string icePassword = "******"; STUNv2Message msg = new STUNv2Message(STUNv2MessageTypesEnum.BindingSuccessResponse); msg.Header.TransactionId = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; msg.AddXORMappedAddressAttribute(IPAddress.Loopback, 55477); var buffer = msg.ToByteBufferStringKey(icePassword, true); string hmac = "HMAC: "; for (int i = 36; i < 56; i++) { hmac += $"{buffer[i]:X2} "; } logger.LogDebug(hmac); logger.LogDebug($"Fingerprint: {buffer[buffer.Length - 4]:X2} {buffer[buffer.Length - 3]:X2} {buffer[buffer.Length - 2]:X2} {buffer[buffer.Length - 1]:X2}."); }
private void ProcessStunMessage(IceCandidate iceCandidate, STUNv2Message stunMessage, IPEndPoint remoteEndPoint) { //logger.LogDebug("STUN message received from remote " + remoteEndPoint + " " + stunMessage.Header.MessageType + "."); if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingRequest) { STUNv2Message stunResponse = new STUNv2Message(STUNv2MessageTypesEnum.BindingSuccessResponse); stunResponse.Header.TransactionId = stunMessage.Header.TransactionId; stunResponse.AddXORMappedAddressAttribute(remoteEndPoint.Address, remoteEndPoint.Port); // ToDo: Check authentication. string localIcePassword = LocalIcePassword; byte[] stunRespBytes = stunResponse.ToByteBufferStringKey(localIcePassword, true); iceCandidate.LocalRtpSocket.SendTo(stunRespBytes, remoteEndPoint); iceCandidate.LastStunRequestReceivedAt = DateTime.Now; iceCandidate.IsStunRemoteExchangeComplete = true; if (_isEncryptionDisabled == true) { iceCandidate.RemoteRtpEndPoint = remoteEndPoint; // Don't need to wait for DTLS negotiation. OnIceConnected?.Invoke(iceCandidate, remoteEndPoint); } if (_remoteIceCandidates != null && !_remoteIceCandidates.Any(x => (x.NetworkAddress == remoteEndPoint.Address.ToString() || x.RemoteAddress == remoteEndPoint.Address.ToString()) && (x.Port == remoteEndPoint.Port || x.RemotePort == remoteEndPoint.Port))) { // This STUN request has come from a socket not in the remote ICE candidates list. Add it so we can send our STUN binding request to it. IceCandidate remoteIceCandidate = new IceCandidate() { Transport = "udp", NetworkAddress = remoteEndPoint.Address.ToString(), Port = remoteEndPoint.Port, CandidateType = IceCandidateTypesEnum.host, MediaType = iceCandidate.MediaType }; logger.LogDebug("Adding missing remote ICE candidate for " + remoteEndPoint + " and media type " + iceCandidate.MediaType + "."); _remoteIceCandidates.Add(remoteIceCandidate); } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingSuccessResponse) { if (_turnServerEndPoint != null && remoteEndPoint.ToString() == _turnServerEndPoint.ToString()) { if (iceCandidate.IsGatheringComplete == false) { var reflexAddressAttribute = stunMessage.Attributes.FirstOrDefault(y => y.AttributeType == STUNv2AttributeTypesEnum.XORMappedAddress) as STUNv2XORAddressAttribute; if (reflexAddressAttribute != null) { iceCandidate.StunRflxIPEndPoint = new IPEndPoint(reflexAddressAttribute.Address, reflexAddressAttribute.Port); iceCandidate.IsGatheringComplete = true; logger.LogDebug("ICE gathering complete for local socket " + iceCandidate.LocalRtpSocket.LocalEndPoint + ", rflx address " + iceCandidate.StunRflxIPEndPoint + "."); } else { iceCandidate.IsGatheringComplete = true; logger.LogDebug("The STUN binding response received on " + iceCandidate.LocalRtpSocket.LocalEndPoint + " from " + remoteEndPoint + " did not have an XORMappedAddress attribute, rlfx address can not be determined."); } } } else { iceCandidate.LastStunResponseReceivedAt = DateTime.Now; if (iceCandidate.IsStunLocalExchangeComplete == false) { iceCandidate.IsStunLocalExchangeComplete = true; logger.LogDebug("WebRTC client STUN exchange complete for call " + CallID + ", candidate local socket " + iceCandidate.LocalRtpSocket.LocalEndPoint + ", remote socket " + remoteEndPoint + "."); SetIceConnectionState(IceConnectionStatesEnum.Connected); } } } else if (stunMessage.Header.MessageType == STUNv2MessageTypesEnum.BindingErrorResponse) { logger.LogWarning("A STUN binding error response was received on " + iceCandidate.LocalRtpSocket.LocalEndPoint + " from " + remoteEndPoint + "."); } else { logger.LogWarning("An unrecognised STUN request was received on " + iceCandidate.LocalRtpSocket.LocalEndPoint + " from " + remoteEndPoint + "."); } }
private void SendStunConnectivityChecks() { try { while (!IsClosed) { try { // If one of the ICE candidates has the remote RTP socket set then the negotiation is complete and the STUN checks are to keep the connection alive. if (LocalIceCandidates.Any(x => x.IsConnected == true)) { var iceCandidate = LocalIceCandidates.First(x => x.IsConnected == true); // Remote RTP endpoint gets set when the DTLS negotiation is finished. if (iceCandidate.RemoteRtpEndPoint != null) { //logger.LogDebug("Sending STUN connectivity check to client " + iceCandidate.RemoteRtpEndPoint + "."); string localUser = LocalIceUser; STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.AddUsernameAttribute(RemoteIceUser + ":" + localUser); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Priority, new byte[] { 0x6e, 0x7f, 0x1e, 0xff })); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.UseCandidate, null)); // Must send this to get DTLS started. byte[] stunReqBytes = stunRequest.ToByteBufferStringKey(RemoteIcePassword, true); iceCandidate.LocalRtpSocket.SendTo(stunReqBytes, iceCandidate.RemoteRtpEndPoint); iceCandidate.LastSTUNSendAt = DateTime.Now; } var secondsSinceLastResponse = DateTime.Now.Subtract(iceCandidate.LastCommunicationAt).TotalSeconds; if (secondsSinceLastResponse > ICE_TIMEOUT_SECONDS) { logger.LogWarning("No STUN response was received on a connected ICE connection for " + secondsSinceLastResponse + "s, closing connection."); iceCandidate.IsDisconnected = true; if (LocalIceCandidates.Any(x => x.IsConnected == true) == false) { // If there are no connected local candidates left close the peer. Close(); break; } } } else { if (_remoteIceCandidates.Count() > 0) { foreach (var localIceCandidate in LocalIceCandidates.Where(x => x.IsStunLocalExchangeComplete == false && x.StunConnectionRequestAttempts < MAXIMUM_STUN_CONNECTION_ATTEMPTS)) { localIceCandidate.StunConnectionRequestAttempts++; // ToDo: Include srflx and relay addresses. // Only supporting UDP candidates at this stage. foreach (var remoteIceCandidate in RemoteIceCandidates.Where(x => x.Transport.ToLower() == "udp" && x.NetworkAddress.NotNullOrBlank() && x.HasConnectionError == false)) { try { IPAddress remoteAddress = IPAddress.Parse(remoteIceCandidate.NetworkAddress); logger.LogDebug("Sending authenticated STUN binding request " + localIceCandidate.StunConnectionRequestAttempts + " from " + localIceCandidate.LocalRtpSocket.LocalEndPoint + " to WebRTC peer at " + remoteIceCandidate.NetworkAddress + ":" + remoteIceCandidate.Port + "."); string localUser = LocalIceUser; STUNv2Message stunRequest = new STUNv2Message(STUNv2MessageTypesEnum.BindingRequest); stunRequest.Header.TransactionId = Guid.NewGuid().ToByteArray().Take(12).ToArray(); stunRequest.AddUsernameAttribute(RemoteIceUser + ":" + localUser); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.Priority, new byte[] { 0x6e, 0x7f, 0x1e, 0xff })); stunRequest.Attributes.Add(new STUNv2Attribute(STUNv2AttributeTypesEnum.UseCandidate, null)); // Must send this to get DTLS started. byte[] stunReqBytes = stunRequest.ToByteBufferStringKey(RemoteIcePassword, true); localIceCandidate.LocalRtpSocket.SendTo(stunReqBytes, new IPEndPoint(IPAddress.Parse(remoteIceCandidate.NetworkAddress), remoteIceCandidate.Port)); localIceCandidate.LastSTUNSendAt = DateTime.Now; } catch (System.Net.Sockets.SocketException sockExcp) { logger.LogWarning($"SocketException sending STUN request to {remoteIceCandidate.NetworkAddress}:{remoteIceCandidate.Port}, removing candidate. {sockExcp.Message}"); remoteIceCandidate.HasConnectionError = true; } } } } } } catch (Exception excp) { logger.LogError("Exception SendStunConnectivityCheck ConnectivityCheck. " + excp); } if (!IsClosed) { Thread.Sleep(ESTABLISHED_STUN_BINDING_PERIOD_MILLISECONDS); } } } catch (Exception excp) { logger.LogError("Exception SendStunConnectivityCheck. " + excp); } }