public SIPDialogue Answer(string contentType, string body, SIPDialogue answeredDialogue, SIPDialogueTransferModesEnum transferMode) { try { logger.Debug("SIPB2BUserAgent Answer."); m_sipDialogue = answeredDialogue; UASStateChanged?.Invoke(this, SIPResponseStatusCodesEnum.Ok, null); SIPResponse uasOkResponse = SIPTransport.GetResponse(m_uasTransaction.TransactionRequest, SIPResponseStatusCodesEnum.Ok, null); m_uasTransaction.SendFinalResponse(uasOkResponse); m_uasTransaction.ACKReceived(m_blackhole, m_blackhole, null); SIPResponse uacOkResponse = SIPTransport.GetResponse(m_uacTransaction.TransactionRequest, SIPResponseStatusCodesEnum.Ok, null); uacOkResponse.Header.Contact = new List <SIPContactHeader>() { new SIPContactHeader(null, new SIPURI(SIPSchemesEnum.sip, m_blackhole)) }; m_uacTransaction.GotResponse(m_blackhole, m_blackhole, uacOkResponse); uacOkResponse.Header.ContentType = contentType; if (!body.IsNullOrBlank()) { uacOkResponse.Body = body; uacOkResponse.Header.ContentLength = body.Length; } CallAnswered.Invoke(this, uacOkResponse); return(null); } catch (Exception excp) { logger.Error("Exception SIPB2BUSerAgent Answer. " + excp.Message); throw; } }
public bool AuthenticateCall() { m_isAuthenticated = false; try { if (m_sipAccount == null) { logger.LogWarning($"Rejecting authentication required call for {m_uasTransaction.TransactionRequestFrom}, SIP account not found."); Reject(SIPResponseStatusCodesEnum.Forbidden, null, null); } else { SIPRequest sipRequest = m_uasTransaction.TransactionRequest; SIPEndPoint localSIPEndPoint = (!sipRequest.Header.ProxyReceivedOn.IsNullOrBlank()) ? SIPEndPoint.ParseSIPEndPoint(sipRequest.Header.ProxyReceivedOn) : sipRequest.LocalSIPEndPoint; SIPEndPoint remoteEndPoint = (!sipRequest.Header.ProxyReceivedFrom.IsNullOrBlank()) ? SIPEndPoint.ParseSIPEndPoint(sipRequest.Header.ProxyReceivedFrom) : sipRequest.RemoteSIPEndPoint; var authenticationResult = SIPRequestAuthenticator.AuthenticateSIPRequest(localSIPEndPoint, remoteEndPoint, sipRequest, m_sipAccount); if (authenticationResult.Authenticated) { if (authenticationResult.WasAuthenticatedByIP) { logger.LogDebug($"New call from {remoteEndPoint} successfully authenticated by IP address."); } else { logger.LogDebug($"New call from {remoteEndPoint} successfully authenticated by digest."); } m_isAuthenticated = true; } else { if (sipRequest.Header.AuthenticationHeader != null) { logger.LogWarning($"Call not authenticated for {m_sipAccount.SIPUsername}@{m_sipAccount.SIPDomain}, responding with {authenticationResult.ErrorResponse}."); } // Send authorisation failure or required response SIPResponse authReqdResponse = SIPResponse.GetResponse(sipRequest, authenticationResult.ErrorResponse, null); authReqdResponse.Header.AuthenticationHeader = authenticationResult.AuthenticationRequiredHeader; m_uasTransaction.SendFinalResponse(authReqdResponse); } } } catch (Exception excp) { logger.LogError("Exception SIPServerUserAgent AuthenticateCall. " + excp.Message); Reject(SIPResponseStatusCodesEnum.InternalServerError, null, null); } return(m_isAuthenticated); }
/// <summary> /// Handler for processing incoming SIP requests. /// </summary> /// <param name="localSIPEndPoint">The end point the request was received on.</param> /// <param name="remoteEndPoint">The end point the request came from.</param> /// <param name="sipRequest">The SIP request received.</param> private Task SIPTransportRequestReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) { if (sipRequest.Header.From != null && sipRequest.Header.From.FromTag != null && sipRequest.Header.To != null && sipRequest.Header.To.ToTag != null) { // This is an in-dialog request that will be handled directly by a user agent instance. } else if (sipRequest.Method == SIPMethodsEnum.INVITE) { bool?callAccepted = IncomingCall?.Invoke(sipRequest); if (callAccepted == false) { // All user agents were already on a call return a busy response. UASInviteTransaction uasTransaction = new UASInviteTransaction(SIPTransport, sipRequest, null); SIPResponse busyResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.BusyHere, null); uasTransaction.SendFinalResponse(busyResponse); } } else { logger.Debug("SIP " + sipRequest.Method + " request received but no processing has been set up for it, rejecting."); SIPResponse notAllowedResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null); return(SIPTransport.SendResponseAsync(notAllowedResponse)); } return(Task.FromResult(0)); }
/// <summary> /// Event handler for receiving a re-INVITE request on an established call. /// In call requests can be used for multitude of different purposes. In this /// example program we're only concerned with re-INVITE requests being used /// to place a call on/off hold. /// </summary> /// <param name="uasTransaction">The user agent server invite transaction that /// was created for the request. It needs to be used for sending responses /// to ensure reliable delivery.</param> private static void ReinviteRequestReceived(UASInviteTransaction uasTransaction) { SIPRequest reinviteRequest = uasTransaction.TransactionRequest; // Re-INVITEs can also be changing the RTP end point. We can update this each time. IPEndPoint dstRtpEndPoint = SDP.GetSDPRTPEndPoint(reinviteRequest.Body); _remoteRtpEndPoint = dstRtpEndPoint; // If the RTP callfow attribute has changed it's most likely due to being placed on/off hold. SDP newSDP = SDP.ParseSDPDescription(reinviteRequest.Body); if (GetRTPStatusAttribute(newSDP) == RTP_ATTRIBUTE_SENDONLY) { Log.LogInformation("Remote call party has placed us on hold."); _holdStatus = HoldStatus.RemotePutOnHold; _ourSDP = GetSDP(_ourRtpSocket.LocalEndPoint as IPEndPoint, RTP_ATTRIBUTE_RECVONLY); var okResponse = SIPTransport.GetResponse(reinviteRequest, SIPResponseStatusCodesEnum.Ok, null); okResponse.Header.ContentType = SDP.SDP_MIME_CONTENTTYPE; okResponse.Body = _ourSDP.ToString(); uasTransaction.SendFinalResponse(okResponse); } else if (GetRTPStatusAttribute(newSDP) == RTP_ATTRIBUTE_SENDRECV && _holdStatus != HoldStatus.None) { Log.LogInformation("Remote call party has taken us off hold."); _holdStatus = HoldStatus.None; _ourSDP = GetSDP(_ourRtpSocket.LocalEndPoint as IPEndPoint, RTP_ATTRIBUTE_SENDRECV); var okResponse = SIPTransport.GetResponse(reinviteRequest, SIPResponseStatusCodesEnum.Ok, null); okResponse.Header.ContentType = SDP.SDP_MIME_CONTENTTYPE; okResponse.Body = _ourSDP.ToString(); uasTransaction.SendFinalResponse(okResponse); } else { Log.LogWarning("Not sure what the remote call party wants us to do..."); // We'll just reply Ok and hope eveything is good. var okResponse = SIPTransport.GetResponse(reinviteRequest, SIPResponseStatusCodesEnum.Ok, null); okResponse.Header.ContentType = SDP.SDP_MIME_CONTENTTYPE; okResponse.Body = _ourSDP.ToString(); uasTransaction.SendFinalResponse(okResponse); } }
/// <summary> /// Attempts to lookup the caller based on the "To" header host. If the host matches a /// hosted domain then an attempt will be made to retrieve the SIP account for the caller. /// If a SIP account is not found for a hosted domain the call is rejected with a 403 response. /// If the host does not match then the caller is treated as a public non-authenticated caller. /// </summary> /// <returns>The caller's SIP account or null for non-hosted domains.</returns> private async Task <ISIPAccount> GetCaller(UASInviteTransaction uasTx) { var invReq = uasTx.TransactionRequest; if (invReq.Header.From == null || invReq.Header.From.FromURI == null) { uasTx.SendFinalResponse(SIPResponse.GetResponse(invReq, SIPResponseStatusCodesEnum.BadRequest, "From header malformed")); return(null); } else { string canonicalDomain = _sipDomainManager.GetCanonicalDomain(invReq.Header.From.FromURI.HostAddress); if (canonicalDomain == null) { // The caller is from a non-hosted domain. Will be treated as a public non-authenticated caller. return(null); } else { Logger.LogDebug($"B2B incoming caller was for hosted domain {canonicalDomain}, looking up caller for {invReq.Header.From.FromURI.User}."); var sipAccount = await _sipAccountDataLayer.GetSIPAccount(invReq.Header.From.FromURI.User, canonicalDomain); if (sipAccount == null) { Logger.LogWarning($"B2B no SIP account found for caller {invReq.Header.From.FromURI.User}@{canonicalDomain}, rejecting."); uasTx.SendFinalResponse(SIPResponse.GetResponse(invReq, SIPResponseStatusCodesEnum.Forbidden, null)); OnAcceptCallFailure?.Invoke(uasTx.TransactionRequest.RemoteSIPEndPoint, CallFailureEnum.NoSIPAccount, invReq); } return(sipAccount); } } }
public void Start(string commandData) { try { m_rtspAppStruct = new RTSPAppStruct(commandData, false); RTSPClient rtspClient = new RTSPClient(); string sdp = rtspClient.GetStreamDescription(m_rtspAppStruct.URL); SIPResponse sipResponse = GetOkResponse(m_clientTransaction.TransactionRequest, sdp); m_clientTransaction.SendFinalResponse(sipResponse); //rtspClient.Start(m_rtspAppStruct.URL, m_clientTransaction.RemoteEndPoint.Address, m_rtpPort); } catch (Exception excp) { logger.Error("Exception RTSPCall StartCall. " + excp.Message); } }
private void Forward(UASInviteTransaction uasTx) { SIPB2BUserAgent b2bua = new SIPB2BUserAgent(_sipTransport, null, uasTx, null); b2bua.CallAnswered += (uac, resp) => ForwardCallAnswered(uac, b2bua); var dst = _getDestination(uasTx); if (dst == null) { Logger.LogInformation($"B2BUA lookup did not return a destination. Rejecting UAS call."); var notFoundResp = SIPResponse.GetResponse(uasTx.TransactionRequest, SIPResponseStatusCodesEnum.NotFound, null); uasTx.SendFinalResponse(notFoundResp); } else { Logger.LogInformation($"B2BUA forwarding call to {dst.Uri}."); b2bua.Call(dst); } }
private async Task Forward(UASInviteTransaction uasTx, ISIPAccount callerSIPAccount) { var invReq = uasTx.TransactionRequest; //uasTx.TransactionStateChanged += (tx) => Logger.LogDebug($"B2B uas tx state changed to {tx.TransactionState}."); //uasTx.TransactionTraceMessage += (tx, msg) => Logger.LogDebug($"B2B uas tx trace message. {msg}"); Logger.LogDebug($"B2B commencing forward for caller {invReq.Header.From.FromURI} to {invReq.URI}."); SIPB2BUserAgent b2bua = new SIPB2BUserAgent(_sipTransport, null, uasTx, callerSIPAccount); bool isAuthenticated = false; if (callerSIPAccount != null) { isAuthenticated = b2bua.AuthenticateCall(); } if (callerSIPAccount == null || isAuthenticated == true) { b2bua.CallAnswered += (uac, resp) => ForwardCallAnswered(uac, b2bua); var dst = await _sipdialPlan.Lookup(uasTx, null); if (dst == null) { Logger.LogInformation($"B2BUA lookup did not return a destination. Rejecting UAS call."); var notFoundResp = SIPResponse.GetResponse(uasTx.TransactionRequest, SIPResponseStatusCodesEnum.NotFound, null); uasTx.SendFinalResponse(notFoundResp); OnAcceptCallFailure?.Invoke(uasTx.TransactionRequest.RemoteSIPEndPoint, CallFailureEnum.NotFound, invReq); } else { Logger.LogInformation($"B2BUA forwarding call to {dst.Uri}."); b2bua.Call(dst); } } }
static void Main() { Console.WriteLine("SIPSorcery Attended Transfer example."); Console.WriteLine("Press 'c' to place a call to the default destination."); Console.WriteLine("Place two simultaneous SIP calls to this program and then press 't'."); Console.WriteLine("Press 'q' or ctrl-c to exit."); // Plumbing code to facilitate a graceful exit. CancellationTokenSource exitCts = new CancellationTokenSource(); // Cancellation token to stop the SIP transport and RTP stream. AddConsoleLogger(); // Set up a default SIP transport. var sipTransport = new SIPTransport(); sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT))); //EnableTraceLogs(sipTransport); // Create two user agents. Each gets configured to answer an incoming call. var userAgent1 = new SIPUserAgent(sipTransport, null); var userAgent2 = new SIPUserAgent(sipTransport, null); userAgent1.OnCallHungup += (dialog) => Log.LogInformation($"UA1: Call hungup by remote party."); userAgent1.ServerCallCancelled += (uas) => Log.LogInformation("UA1: Incoming call cancelled by caller."); userAgent2.OnCallHungup += (dialog) => Log.LogInformation($"UA2: Call hungup by remote party."); userAgent2.ServerCallCancelled += (uas) => Log.LogInformation("UA2: Incoming call cancelled by caller."); userAgent2.OnTransferNotify += (sipFrag) => { if (!string.IsNullOrEmpty(sipFrag)) { Log.LogInformation($"UA2: Transfer status update: {sipFrag.Trim()}."); if (sipFrag?.Contains("SIP/2.0 200") == true) { // The transfer attempt got a successful answer. Can hangup the call. userAgent2.Hangup(); exitCts.Cancel(); } } }; sipTransport.SIPTransportRequestReceived += async(localEndPoint, remoteEndPoint, sipRequest) => { if (sipRequest.Header.From != null && sipRequest.Header.From.FromTag != null && sipRequest.Header.To != null && sipRequest.Header.To.ToTag != null) { // This is an in-dialog request that will be handled directly by a user agent instance. } else if (sipRequest.Method == SIPMethodsEnum.INVITE) { if (!userAgent1.IsCallActive || !userAgent2.IsCallActive) { SIPUserAgent activeAgent = (!userAgent1.IsCallActive) ? userAgent1 : userAgent2; string agentDesc = (!userAgent1.IsCallActive) ? "UA1" : "UA2"; Log.LogInformation($"{agentDesc}: Incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); var incomingCall = activeAgent.AcceptCall(sipRequest); var rtpAVSession = new RtpAVSession(new AudioOptions { AudioSource = AudioSourcesEnum.CaptureDevice }, null); await activeAgent.Answer(incomingCall, rtpAVSession); Log.LogInformation($"{agentDesc}: Answered incoming call from {sipRequest.Header.From.FriendlyDescription()} at {remoteEndPoint}."); } else { // If both user agents are already on a call return a busy response. Log.LogWarning($"Busy response returned for incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); UASInviteTransaction uasTransaction = new UASInviteTransaction(sipTransport, sipRequest, null); SIPResponse busyResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.BusyHere, null); uasTransaction.SendFinalResponse(busyResponse); } } else { Log.LogDebug($"SIP {sipRequest.Method} request received but no processing has been set up for it, rejecting."); SIPResponse notAllowedResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null); await sipTransport.SendResponseAsync(notAllowedResponse); } }; // At this point the call has been initiated and everything will be handled in an event handler. Task.Run(async() => { try { while (!exitCts.Token.WaitHandle.WaitOne(0)) { var keyProps = Console.ReadKey(); if (keyProps.KeyChar == 'c') { // Place an outgoing call using the first free user agent. SIPUserAgent freeAgent = (!userAgent1.IsCallActive) ? userAgent1 : (!userAgent2.IsCallActive) ? userAgent2 : null; if (freeAgent != null) { var rtpAVSession = new RtpAVSession(new AudioOptions { AudioSource = AudioSourcesEnum.CaptureDevice }, null); bool callResult = await freeAgent.Call(DEFAULT_DESTINATION_SIP_URI, null, null, rtpAVSession); Log.LogInformation($"Call attempt {((callResult) ? "successfull" : "failed")}."); } else { Log.LogWarning("Both user agents are already on calls."); } } if (keyProps.KeyChar == 't') { // Initiate the attended transfer. if (userAgent1.IsCallActive && userAgent2.IsCallActive) { bool result = await userAgent2.AttendedTransfer(userAgent1.Dialogue, TimeSpan.FromSeconds(TRANSFER_TIMEOUT_SECONDS), exitCts.Token); if (!result) { Log.LogWarning($"Attended transfer failed."); } } else { Log.LogWarning("There need to be two active calls before the attended transfer can occur."); } } else if (keyProps.KeyChar == 'q') { // Quit application. exitCts.Cancel(); } } } catch (Exception excp) { SIPSorcery.Sys.Log.Logger.LogError($"Exception Key Press listener. {excp.Message}."); } }); // Ctrl-c will gracefully exit the call at any point. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; exitCts.Cancel(); }; // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. exitCts.Token.WaitHandle.WaitOne(); #region Cleanup. Log.LogInformation("Exiting..."); userAgent1?.Hangup(); userAgent2?.Hangup(); // Give any BYE or CANCEL requests time to be transmitted. Log.LogInformation("Waiting 1s for calls to be cleaned up..."); Task.Delay(1000).Wait(); if (sipTransport != null) { Log.LogInformation("Shutting down SIP transport..."); sipTransport.Shutdown(); } #endregion }
public bool AuthenticateCall() { m_isAuthenticated = false; try { if (SIPAuthenticateRequest_External == null) { // No point trying to authenticate if we haven't been given an authentication delegate. Reject(SIPResponseStatusCodesEnum.InternalServerError, null, null); } else if (GetSIPAccount_External == null) { // No point trying to authenticate if we haven't been given a delegate to load the SIP account. Reject(SIPResponseStatusCodesEnum.InternalServerError, null, null); } else { m_sipAccount = GetSIPAccount_External(s => s.SIPUsername == m_sipUsername && s.SIPDomain == m_sipDomain); if (m_sipAccount == null) { Reject(SIPResponseStatusCodesEnum.Forbidden, null, null); } else { SIPRequest sipRequest = m_uasTransaction.TransactionRequest; SIPEndPoint localSIPEndPoint = (!sipRequest.Header.ProxyReceivedOn.IsNullOrBlank()) ? SIPEndPoint.ParseSIPEndPoint(sipRequest.Header.ProxyReceivedOn) : sipRequest.LocalSIPEndPoint; SIPEndPoint remoteEndPoint = (!sipRequest.Header.ProxyReceivedFrom.IsNullOrBlank()) ? SIPEndPoint.ParseSIPEndPoint(sipRequest.Header.ProxyReceivedFrom) : sipRequest.RemoteSIPEndPoint; SIPRequestAuthenticationResult authenticationResult = SIPAuthenticateRequest_External(localSIPEndPoint, remoteEndPoint, sipRequest, m_sipAccount); if (authenticationResult.Authenticated) { if (authenticationResult.WasAuthenticatedByIP) { } else { } SetOwner(m_sipAccount.Owner, m_sipAccount.AdminMemberId); m_isAuthenticated = true; } else { // Send authorisation failure or required response SIPResponse authReqdResponse = SIPTransport.GetResponse(sipRequest, authenticationResult.ErrorResponse, null); authReqdResponse.Header.AuthenticationHeader = authenticationResult.AuthenticationRequiredHeader; m_uasTransaction.SendFinalResponse(authReqdResponse); } } } } catch (Exception excp) { Logger.Logger.Error("Exception SIPServerUserAgent AuthenticateCall. ->" + excp.Message); Reject(SIPResponseStatusCodesEnum.InternalServerError, null, null); } return(m_isAuthenticated); }
/// <summary> /// Handler for when an in dialog request is received on an established call. /// Typical types of request will be re-INVITES for things like putting a call on or /// off hold and REFER requests for transfers. Some in dialog request types, such /// as re-INVITES have specific events so they can be bubbled up to the /// application to deal with. /// </summary> /// <param name="sipRequest">The in dialog request received.</param> private async Task DialogRequestReceivedAsync(SIPRequest sipRequest) { // Make sure the request matches our dialog and is not a stray. // A dialog request should match on to tag, from tag and call ID. We'll be more // accepting just in case the sender got the tags wrong. if (Dialogue == null || sipRequest.Header.CallId != Dialogue.CallId) { var noCallLegResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist, null); var sendResult = await SendResponseAsync(noCallLegResponse); if (sendResult != SocketError.Success) { logger.LogWarning($"SIPUserAgent send response failed in DialogRequestReceivedAsync with {sendResult}."); } } else { if (sipRequest.Method == SIPMethodsEnum.BYE) { logger.LogInformation($"Remote call party hungup {sipRequest.StatusLine}."); Dialogue.DialogueState = SIPDialogueStateEnum.Terminated; SIPResponse okResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); await SendResponseAsync(okResponse).ConfigureAwait(false); CallEnded(); } else if (sipRequest.Method == SIPMethodsEnum.INVITE) { logger.LogDebug($"Re-INVITE request received {sipRequest.StatusLine}."); UASInviteTransaction reInviteTransaction = new UASInviteTransaction(m_transport, sipRequest, m_outboundProxy); try { var answerSdp = await MediaSession.RemoteReInvite(sipRequest.Body).ConfigureAwait(false); Dialogue.RemoteSDP = sipRequest.Body; Dialogue.SDP = answerSdp; Dialogue.RemoteCSeq = sipRequest.Header.CSeq; var okResponse = reInviteTransaction.GetOkResponse(SDP.SDP_MIME_CONTENTTYPE, Dialogue.SDP); reInviteTransaction.SendFinalResponse(okResponse); } catch (Exception ex) { logger.LogError(ex, "MediaSession can't process the re-INVITE request."); if (OnReinviteRequest == null) { // The application isn't prepared to accept re-INVITE requests and we can't work out what it was for. // We'll reject as gently as we can to try and not lose the call. SIPResponse notAcceptableResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.NotAcceptable, null); reInviteTransaction.SendFinalResponse(notAcceptableResponse); } else { // The application is going to handle the re-INVITE request. We'll send a Trying response as a precursor. SIPResponse tryingResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Trying, null); await reInviteTransaction.SendProvisionalResponse(tryingResponse); OnReinviteRequest.Invoke(reInviteTransaction); } } } else if (sipRequest.Method == SIPMethodsEnum.OPTIONS) { //Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "OPTIONS request for established dialogue " + dialogue.DialogueName + ".", dialogue.Owner)); SIPResponse okResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); okResponse.Body = Dialogue.RemoteSDP; okResponse.Header.ContentLength = okResponse.Body.Length; okResponse.Header.ContentType = m_sdpContentType; await SendResponseAsync(okResponse); } else if (sipRequest.Method == SIPMethodsEnum.MESSAGE) { //Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "MESSAGE for call " + sipRequest.URI.ToString() + ": " + sipRequest.Body + ".", dialogue.Owner)); SIPResponse okResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); await m_transport.SendResponseAsync(okResponse); } else if (sipRequest.Method == SIPMethodsEnum.REFER) { if (sipRequest.Header.ReferTo.IsNullOrBlank()) { // A REFER request must have a Refer-To header. //Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Bad REFER request, no Refer-To header.", dialogue.Owner)); SIPResponse invalidResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.BadRequest, "Missing mandatory Refer-To header"); await SendResponseAsync(invalidResponse); } else { //TODO: Add handling logic for in transfer requests from the remote call party. } } else if (sipRequest.Method == SIPMethodsEnum.NOTIFY) { SIPResponse okResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); await SendResponseAsync(okResponse); if (sipRequest.Body?.Length > 0 && sipRequest.Header.ContentType?.Contains(m_sipReferContentType) == true) { OnTransferNotify?.Invoke(sipRequest.Body); } } } }
//private delegate void MediaSampleReadyDelegate(SDPMediaTypesEnum mediaType, uint duration, byte[] sample); //private static event MediaSampleReadyDelegate OnMediaFromSIPSampleReady; static void Main(string[] args) { Console.WriteLine("SIPSorcery SIP to WebRTC example."); Console.WriteLine("Press ctrl-c to exit."); // Plumbing code to facilitate a graceful exit. CancellationTokenSource exitCts = new CancellationTokenSource(); // Cancellation token to stop the SIP transport and RTP stream. Log = AddConsoleLogger(); // Start web socket. Console.WriteLine("Starting web socket server..."); _webSocketServer = new WebSocketServer(IPAddress.Any, WEBSOCKET_PORT, true); _webSocketServer.SslConfiguration.ServerCertificate = new X509Certificate2(WEBSOCKET_CERTIFICATE_PATH); _webSocketServer.SslConfiguration.CheckCertificateRevocation = false; //_webSocketServer.Log.Level = WebSocketSharp.LogLevel.Debug; _webSocketServer.AddWebSocketService <SDPExchange>("/", (sdpExchanger) => { sdpExchanger.WebSocketOpened += SendSDPOffer; sdpExchanger.SDPAnswerReceived += SDPAnswerReceived; }); _webSocketServer.Start(); Console.WriteLine($"Waiting for browser web socket connection to {_webSocketServer.Address}:{_webSocketServer.Port}..."); // Set up a default SIP transport. var sipTransport = new SIPTransport(); sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT))); //EnableTraceLogs(sipTransport); RTPSession rtpSession = null; // Create a SIP user agent to receive a call from a remote SIP client. // Wire up event handlers for the different stages of the call. var userAgent = new SIPUserAgent(sipTransport, null); // We're only answering SIP calls, not placing them. userAgent.OnCallHungup += (dialog) => { Log.LogInformation($"Call hungup by remote party."); exitCts.Cancel(); }; userAgent.ServerCallCancelled += (uas) => Log.LogInformation("Incoming call cancelled by caller."); sipTransport.SIPTransportRequestReceived += async(localEndPoint, remoteEndPoint, sipRequest) => { if (sipRequest.Header.From != null && sipRequest.Header.From.FromTag != null && sipRequest.Header.To != null && sipRequest.Header.To.ToTag != null) { // This is an in-dialog request that will be handled directly by a user agent instance. } else if (sipRequest.Method == SIPMethodsEnum.INVITE) { if (userAgent?.IsCallActive == true) { Log.LogWarning($"Busy response returned for incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); // If we are already on a call return a busy response. UASInviteTransaction uasTransaction = new UASInviteTransaction(sipTransport, sipRequest, null); SIPResponse busyResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.BusyHere, null); uasTransaction.SendFinalResponse(busyResponse); } else { Log.LogInformation($"Incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); var incomingCall = userAgent.AcceptCall(sipRequest); rtpSession = new RTPSession(false, false, false); rtpSession.AcceptRtpFromAny = true; MediaStreamTrack audioTrack = new MediaStreamTrack(SDPMediaTypesEnum.audio, false, new List <SDPMediaFormat> { new SDPMediaFormat(SDPMediaFormatsEnum.PCMU) }); rtpSession.addTrack(audioTrack); await userAgent.Answer(incomingCall, rtpSession); rtpSession.OnRtpPacketReceived += (ep, mediaType, rtpPacket) => ForwardMedia(mediaType, rtpPacket); Log.LogInformation($"Answered incoming call from {sipRequest.Header.From.FriendlyDescription()} at {remoteEndPoint}."); } } else { Log.LogDebug($"SIP {sipRequest.Method} request received but no processing has been set up for it, rejecting."); SIPResponse notAllowedResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null); await sipTransport.SendResponseAsync(notAllowedResponse); } }; // Ctrl-c will gracefully exit the call at any point. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; exitCts.Cancel(); }; // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. exitCts.Token.WaitHandle.WaitOne(); #region Cleanup. Log.LogInformation("Exiting..."); rtpSession?.Close("app exit"); if (userAgent != null) { if (userAgent.IsCallActive) { Log.LogInformation($"Hanging up call to {userAgent?.CallDescriptor?.To}."); userAgent.Hangup(); } // Give the BYE or CANCEL request time to be transmitted. Log.LogInformation("Waiting 1s for call to clean up..."); Task.Delay(1000).Wait(); } if (sipTransport != null) { Log.LogInformation("Shutting down SIP transport..."); sipTransport.Shutdown(); } #endregion }
public bool AuthenticateCall() { m_isAuthenticated = false; try { if (SIPAuthenticateRequest_External == null) { // No point trying to authenticate if we haven't been given an authentication delegate. Reject(SIPResponseStatusCodesEnum.InternalServerError, null, null); } else if (GetSIPAccount_External == null) { // No point trying to authenticate if we haven't been given a delegate to load the SIP account. Reject(SIPResponseStatusCodesEnum.InternalServerError, null, null); } else { m_sipAccount = GetSIPAccount_External(m_sipUsername, m_sipDomain); if (m_sipAccount == null) { Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Rejecting authentication required call for " + m_sipUsername + "@" + m_sipDomain + ", SIP account not found.", null)); Reject(SIPResponseStatusCodesEnum.Forbidden, null, null); } else { SIPRequest sipRequest = m_uasTransaction.TransactionRequest; SIPEndPoint localSIPEndPoint = (!sipRequest.Header.ProxyReceivedOn.IsNullOrBlank()) ? SIPEndPoint.ParseSIPEndPoint(sipRequest.Header.ProxyReceivedOn) : sipRequest.LocalSIPEndPoint; SIPEndPoint remoteEndPoint = (!sipRequest.Header.ProxyReceivedFrom.IsNullOrBlank()) ? SIPEndPoint.ParseSIPEndPoint(sipRequest.Header.ProxyReceivedFrom) : sipRequest.RemoteSIPEndPoint; SIPRequestAuthenticationResult authenticationResult = SIPAuthenticateRequest_External(localSIPEndPoint, remoteEndPoint, sipRequest, m_sipAccount, Log_External); if (authenticationResult.Authenticated) { if (authenticationResult.WasAuthenticatedByIP) { Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "New call from " + remoteEndPoint.ToString() + " successfully authenticated by IP address.", m_sipAccount.Owner)); } else { Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "New call from " + remoteEndPoint.ToString() + " successfully authenticated by digest.", m_sipAccount.Owner)); } SetOwner(m_sipAccount.Owner, m_sipAccount.AdminMemberId); m_isAuthenticated = true; } else { // Send authorisation failure or required response SIPResponse authReqdResponse = SIPResponse.GetResponse(sipRequest, authenticationResult.ErrorResponse, null); authReqdResponse.Header.AuthenticationHeader = authenticationResult.AuthenticationRequiredHeader; Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Call not authenticated for " + m_sipUsername + "@" + m_sipDomain + ", responding with " + authenticationResult.ErrorResponse + ".", null)); m_uasTransaction.SendFinalResponse(authReqdResponse); } } } } catch (Exception excp) { logger.LogError("Exception SIPServerUserAgent AuthenticateCall. " + excp.Message); Reject(SIPResponseStatusCodesEnum.InternalServerError, null, null); } return(m_isAuthenticated); }
static void Main() { Console.WriteLine("SIPSorcery call hold example."); Console.WriteLine("Press ctrl-c to exit."); // Plumbing code to facilitate a graceful exit. CancellationTokenSource exitCts = new CancellationTokenSource(); // Cancellation token to stop the SIP transport and RTP stream. AddConsoleLogger(); // Set up a default SIP transport. var sipTransport = new SIPTransport(); sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT))); EnableTraceLogs(sipTransport); // Create two user agents. Each gets configured to answer an incoming call. var userAgent1 = new SIPUserAgent(sipTransport, null); var userAgent2 = new SIPUserAgent(sipTransport, null); // Only one of the user agents can use the microphone and speaker. The one designated // as the active agent gets the devices. SIPUserAgent activeUserAgent = null; RTPMediaSession activeRtpSession = null; // Get the default speaker. var(audioOutEvent, audioOutProvider) = GetAudioOutputDevice(); m_audioOutProvider = audioOutProvider; WaveInEvent waveInEvent = GetAudioInputDevice(); userAgent1.OnCallHungup += () => Log.LogInformation($"UA1: Call hungup by remote party."); userAgent1.ServerCallCancelled += (uas) => Log.LogInformation("UA1: Incoming call cancelled by caller."); userAgent2.OnCallHungup += () => Log.LogInformation($"UA2: Call hungup by remote party."); userAgent2.ServerCallCancelled += (uas) => Log.LogInformation("UA2: Incoming call cancelled by caller."); userAgent2.OnTransferNotify += (sipFrag) => { if (!string.IsNullOrEmpty(sipFrag)) { Log.LogInformation($"UA2: Transfer status update: {sipFrag.Trim()}."); if (sipFrag?.Contains("SIP/2.0 200") == true) { // The transfer attempt got a succesful answer. Can hangup the call. userAgent2.Hangup(); exitCts.Cancel(); } } }; sipTransport.SIPTransportRequestReceived += (locelEndPoint, remoteEndPoint, sipRequest) => { if (sipRequest.Header.From != null && sipRequest.Header.From.FromTag != null && sipRequest.Header.To != null && sipRequest.Header.To.ToTag != null) { // This is an in-dialog request that will be handled directly by a user agent instance. } else if (sipRequest.Method == SIPMethodsEnum.INVITE) { if (!userAgent1.IsCallActive) { Log.LogInformation($"UA1: Incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); var incomingCall = userAgent1.AcceptCall(sipRequest); var rtpMediaSession = new RTPMediaSession(SDPMediaTypesEnum.audio, new SDPMediaFormat(SDPMediaFormatsEnum.PCMU), AddressFamily.InterNetwork); rtpMediaSession.RemotePutOnHold += () => Log.LogInformation("UA1: Remote call party has placed us on hold."); rtpMediaSession.RemoteTookOffHold += () => Log.LogInformation("UA1: Remote call party took us off hold."); userAgent1.Answer(incomingCall, rtpMediaSession) .ContinueWith(task => { activeUserAgent = userAgent1; activeRtpSession = rtpMediaSession; activeRtpSession.OnRtpPacketReceived += PlaySample; waveInEvent.StartRecording(); Log.LogInformation($"UA1: Answered incoming call from {sipRequest.Header.From.FriendlyDescription()} at {remoteEndPoint}."); }, exitCts.Token); } else if (!userAgent2.IsCallActive) { Log.LogInformation($"UA2: Incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); var incomingCall = userAgent2.AcceptCall(sipRequest); var rtpMediaSession = new RTPMediaSession(SDPMediaTypesEnum.audio, new SDPMediaFormat(SDPMediaFormatsEnum.PCMU), AddressFamily.InterNetwork); rtpMediaSession.RemotePutOnHold += () => Log.LogInformation("UA2: Remote call party has placed us on hold."); rtpMediaSession.RemoteTookOffHold += () => Log.LogInformation("UA2: Remote call party took us off hold."); userAgent2.Answer(incomingCall, rtpMediaSession) .ContinueWith(task => { activeRtpSession.OnRtpPacketReceived -= PlaySample; activeUserAgent = userAgent2; activeRtpSession = rtpMediaSession; activeRtpSession.PutOnHold(); activeRtpSession.OnRtpPacketReceived += PlaySample; Log.LogInformation($"UA2: Answered incoming call from {sipRequest.Header.From.FriendlyDescription()} at {remoteEndPoint}."); }, exitCts.Token); } else { // If both user agents are already on a call return a busy response. Log.LogWarning($"Busy response returned for incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); UASInviteTransaction uasTransaction = new UASInviteTransaction(sipTransport, sipRequest, null); SIPResponse busyResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.BusyHere, null); uasTransaction.SendFinalResponse(busyResponse); } } else { Log.LogDebug($"SIP {sipRequest.Method} request received but no processing has been set up for it, rejecting."); SIPResponse notAllowedResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null); return(sipTransport.SendResponseAsync(notAllowedResponse)); } return(Task.FromResult(0)); }; // Wire up the RTP send session to the audio input device. uint rtpSendTimestamp = 0; waveInEvent.DataAvailable += (object sender, WaveInEventArgs args) => { byte[] sample = new byte[args.Buffer.Length / 2]; int sampleIndex = 0; for (int index = 0; index < args.BytesRecorded; index += 2) { var ulawByte = NAudio.Codecs.MuLawEncoder.LinearToMuLawSample(BitConverter.ToInt16(args.Buffer, index)); sample[sampleIndex++] = ulawByte; } if (activeRtpSession != null) { activeRtpSession.SendAudioFrame(rtpSendTimestamp, (int)SDPMediaFormatsEnum.PCMU, sample); rtpSendTimestamp += (uint)sample.Length; } }; // At this point the call has been initiated and everything will be handled in an event handler. Task.Run(async() => { try { while (!exitCts.Token.WaitHandle.WaitOne(0)) { var keyProps = Console.ReadKey(); if (keyProps.KeyChar == 't') { if (userAgent1.IsCallActive && userAgent2.IsCallActive) { bool result = await userAgent2.AttendedTransfer(userAgent1.Dialogue, TimeSpan.FromSeconds(TRANSFER_TIMEOUT_SECONDS), exitCts.Token); if (!result) { Log.LogWarning($"Attended transfer failed."); } } else { Log.LogWarning("There need to be two active calls before the attended transfer can occur."); } } else if (keyProps.KeyChar == 'q') { // Quit application. exitCts.Cancel(); } } } catch (Exception excp) { SIPSorcery.Sys.Log.Logger.LogError($"Exception Key Press listener. {excp.Message}."); } }); // Ctrl-c will gracefully exit the call at any point. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; exitCts.Cancel(); }; // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. exitCts.Token.WaitHandle.WaitOne(); #region Cleanup. Log.LogInformation("Exiting..."); userAgent1?.Hangup(); userAgent2?.Hangup(); waveInEvent?.StopRecording(); audioOutEvent?.Stop(); // Give any BYE or CANCEL requests time to be transmitted. Log.LogInformation("Waiting 1s for calls to be cleaned up..."); Task.Delay(1000).Wait(); SIPSorcery.Net.DNSManager.Stop(); if (sipTransport != null) { Log.LogInformation("Shutting down SIP transport..."); sipTransport.Shutdown(); } #endregion }
static void Main() { Console.WriteLine("SIPSorcery Call Hold and Blind Transfer example."); Console.WriteLine("Press 'c' to initiate a call to the default destination."); Console.WriteLine("Press 'h' to place an established call on and off hold."); Console.WriteLine("Press 'H' to hangup an established call."); Console.WriteLine("Press 't' to request a blind transfer on an established call."); Console.WriteLine("Press 'q' or ctrl-c to exit."); // Plumbing code to facilitate a graceful exit. CancellationTokenSource exitCts = new CancellationTokenSource(); // Cancellation token to stop the SIP transport and RTP stream. AddConsoleLogger(); // Set up a default SIP transport. var sipTransport = new SIPTransport(); sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT))); Console.WriteLine($"Listening for incoming calls on: {sipTransport.GetSIPChannels().First().ListeningEndPoint}."); EnableTraceLogs(sipTransport); _currentDir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); RtpAVSession rtpAVSession = null; // Create a client/server user agent to place a call to a remote SIP server along with event handlers for the different stages of the call. var userAgent = new SIPUserAgent(sipTransport, null); userAgent.RemotePutOnHold += () => Log.LogInformation("Remote call party has placed us on hold."); userAgent.RemoteTookOffHold += () => Log.LogInformation("Remote call party took us off hold."); sipTransport.SIPTransportRequestReceived += async(localEndPoint, remoteEndPoint, sipRequest) => { if (sipRequest.Header.From != null && sipRequest.Header.From.FromTag != null && sipRequest.Header.To != null && sipRequest.Header.To.ToTag != null) { // This is an in-dialog request that will be handled directly by a user agent instance. } else if (sipRequest.Method == SIPMethodsEnum.INVITE) { if (userAgent?.IsCallActive == true) { Log.LogWarning($"Busy response returned for incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); // If we are already on a call return a busy response. UASInviteTransaction uasTransaction = new UASInviteTransaction(sipTransport, sipRequest, null); SIPResponse busyResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.BusyHere, null); uasTransaction.SendFinalResponse(busyResponse); } else { Log.LogInformation($"Incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); var incomingCall = userAgent.AcceptCall(sipRequest); rtpAVSession = new RtpAVSession(new AudioOptions { AudioSource = AudioSourcesEnum.CaptureDevice }, null); await userAgent.Answer(incomingCall, rtpAVSession); Log.LogInformation($"Answered incoming call from {sipRequest.Header.From.FriendlyDescription()} at {remoteEndPoint}."); } } else { Log.LogDebug($"SIP {sipRequest.Method} request received but no processing has been set up for it, rejecting."); SIPResponse notAllowedResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null); await sipTransport.SendResponseAsync(notAllowedResponse); } }; // At this point the call has been initiated and everything will be handled in an event handler. Task.Run(async() => { try { while (!exitCts.Token.WaitHandle.WaitOne(0)) { var keyProps = Console.ReadKey(); if (keyProps.KeyChar == 'c') { if (!userAgent.IsCallActive) { rtpAVSession = new RtpAVSession(new AudioOptions { AudioSource = AudioSourcesEnum.CaptureDevice }, null); bool callResult = await userAgent.Call(DEFAULT_DESTINATION_SIP_URI, SIP_USERNAME, SIP_PASSWORD, rtpAVSession); Log.LogInformation($"Call attempt {((callResult) ? "successfull" : "failed")}."); } else { Log.LogWarning("There is already an active call."); } } else if (keyProps.KeyChar == 'h') { // Place call on/off hold. if (userAgent.IsCallActive) { if (userAgent.IsOnLocalHold) { Log.LogInformation("Taking the remote call party off hold."); userAgent.TakeOffHold(); await(userAgent.MediaSession as RtpAVSession).SetSources(new AudioOptions { AudioSource = AudioSourcesEnum.CaptureDevice }, null); } else { Log.LogInformation("Placing the remote call party on hold."); userAgent.PutOnHold(); await(userAgent.MediaSession as RtpAVSession).SetSources(new AudioOptions { AudioSource = AudioSourcesEnum.Music, SourceFiles = new Dictionary <SDPMediaFormatsEnum, string> { { SDPMediaFormatsEnum.PCMU, _currentDir + "/" + AUDIO_FILE_PCMU } } }, null); } } else { Log.LogWarning("There is no active call to put on hold."); } } else if (keyProps.KeyChar == 'H') { if (userAgent.IsCallActive) { Log.LogInformation("Hanging up call."); userAgent.Hangup(); } } else if (keyProps.KeyChar == 't') { // Initiate a blind transfer to the remote call party. if (userAgent.IsCallActive) { var transferURI = SIPURI.ParseSIPURI(TRANSFER_DESTINATION_SIP_URI); bool result = await userAgent.BlindTransfer(transferURI, TimeSpan.FromSeconds(TRANSFER_TIMEOUT_SECONDS), exitCts.Token); if (result) { // If the transfer was accepted the original call will already have been hungup. // Wait a second for the transfer NOTIFY request to arrive. await Task.Delay(1000); exitCts.Cancel(); } else { Log.LogWarning($"Transfer to {TRANSFER_DESTINATION_SIP_URI} failed."); } } else { Log.LogWarning("There is no active call to transfer."); } } else if (keyProps.KeyChar == 'q') { // Quit application. exitCts.Cancel(); } } } catch (Exception excp) { SIPSorcery.Sys.Log.Logger.LogError($"Exception Key Press listener. {excp.Message}."); } }); // Ctrl-c will gracefully exit the call at any point. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; exitCts.Cancel(); }; // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. exitCts.Token.WaitHandle.WaitOne(); #region Cleanup. Log.LogInformation("Exiting..."); rtpAVSession?.Close("app exit"); if (userAgent != null) { if (userAgent.IsCallActive) { Log.LogInformation($"Hanging up call to {userAgent?.CallDescriptor?.To}."); userAgent.Hangup(); } // Give the BYE or CANCEL request time to be transmitted. Log.LogInformation("Waiting 1s for call to clean up..."); Task.Delay(1000).Wait(); } SIPSorcery.Net.DNSManager.Stop(); if (sipTransport != null) { Log.LogInformation("Shutting down SIP transport..."); sipTransport.Shutdown(); } #endregion }
public void GotRequest(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) { try { // Used in the proxy monitor messages only, plays no part in request routing. string fromUser = (sipRequest.Header.From != null) ? sipRequest.Header.From.FromURI.User : null; string fromURIStr = (sipRequest.Header.From != null) ? sipRequest.Header.From.FromURI.ToString() : "null"; //string toUser = (sipRequest.Header.To != null) ? sipRequest.Header.To.ToURI.User : null; //string summaryStr = "req " + sipRequest.Method + " from=" + fromUser + ", to=" + toUser + ", " + remoteEndPoint.ToString(); //logger.Debug("AppServerCore GotRequest " + sipRequest.Method + " from " + remoteEndPoint.ToString() + " callid=" + sipRequest.Header.CallId + "."); SIPDialogue dialogue = null; // Check dialogue requests for an existing dialogue. if ((sipRequest.Method == SIPMethodsEnum.BYE || sipRequest.Method == SIPMethodsEnum.INFO || sipRequest.Method == SIPMethodsEnum.INVITE || sipRequest.Method == SIPMethodsEnum.MESSAGE || sipRequest.Method == SIPMethodsEnum.NOTIFY || sipRequest.Method == SIPMethodsEnum.OPTIONS || sipRequest.Method == SIPMethodsEnum.REFER) && sipRequest.Header.From != null && sipRequest.Header.From.FromTag != null && sipRequest.Header.To != null && sipRequest.Header.To.ToTag != null) { dialogue = m_sipDialogueManager.GetDialogue(sipRequest); } if (dialogue != null && sipRequest.Method != SIPMethodsEnum.ACK) { FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Matching dialogue found for " + sipRequest.Method + " to " + sipRequest.URI.ToString() + " from " + remoteEndPoint + ".", dialogue.Owner)); if (sipRequest.Method != SIPMethodsEnum.REFER) { m_sipDialogueManager.ProcessInDialogueRequest(localSIPEndPoint, remoteEndPoint, sipRequest, dialogue); } else { m_sipDialogueManager.ProcessInDialogueReferRequest(localSIPEndPoint, remoteEndPoint, sipRequest, dialogue, m_callManager.BlindTransfer); } } else if (sipRequest.Method == SIPMethodsEnum.CANCEL) { #region CANCEL request handling. UASInviteTransaction inviteTransaction = (UASInviteTransaction)m_sipTransport.GetTransaction(SIPTransaction.GetRequestTransactionId(sipRequest.Header.Vias.TopViaHeader.Branch, SIPMethodsEnum.INVITE)); if (inviteTransaction != null) { FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Cancelling call for " + sipRequest.URI.ToString() + ".", fromUser)); SIPCancelTransaction cancelTransaction = m_sipTransport.CreateCancelTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, inviteTransaction); cancelTransaction.GotRequest(localSIPEndPoint, remoteEndPoint, sipRequest); } else { FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "No matching transaction was found for CANCEL to " + sipRequest.URI.ToString() + ".", fromUser)); SIPResponse noCallLegResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist, null); m_sipTransport.SendResponse(noCallLegResponse); } #endregion } else if (sipRequest.Method == SIPMethodsEnum.BYE) { FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "No dialogue matched for BYE to " + sipRequest.URI.ToString() + ".", fromUser)); SIPResponse noCallLegResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist, null); m_sipTransport.SendResponse(noCallLegResponse); } else if (sipRequest.Method == SIPMethodsEnum.REFER) { FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "No dialogue matched for REFER to " + sipRequest.URI.ToString() + ".", fromUser)); SIPResponse noCallLegResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist, null); m_sipTransport.SendResponse(noCallLegResponse); } else if (sipRequest.Method == SIPMethodsEnum.ACK) { FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "No transaction matched for ACK for " + sipRequest.URI.ToString() + ".", fromUser)); } else if (sipRequest.Method == SIPMethodsEnum.INVITE) { #region INVITE request processing. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "AppServerCore INVITE received, uri=" + sipRequest.URI.ToString() + ", cseq=" + sipRequest.Header.CSeq + ".", null)); if (sipRequest.URI.User == m_dispatcherUsername) { // Incoming call from monitoring process checking the application server is still running. UASInviteTransaction uasTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy, true); //uasTransaction.CDR = null; SIPServerUserAgent incomingCall = new SIPServerUserAgent(m_sipTransport, m_outboundProxy, sipRequest.URI.User, sipRequest.URI.Host, SIPCallDirection.In, null, null, null, uasTransaction); //incomingCall.NoCDR(); uasTransaction.NewCallReceived += (local, remote, transaction, request) => { m_callManager.QueueNewCall(incomingCall); }; uasTransaction.GotRequest(localSIPEndPoint, remoteEndPoint, sipRequest); } else if (GetCanonicalDomain_External(sipRequest.Header.From.FromUserField.URI.Host, false) != null) { // Call identified as outgoing call for application server serviced domain. string fromDomain = GetCanonicalDomain_External(sipRequest.Header.From.FromUserField.URI.Host, false); UASInviteTransaction uasTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy); SIPServerUserAgent outgoingCall = new SIPServerUserAgent(m_sipTransport, m_outboundProxy, fromUser, fromDomain, SIPCallDirection.Out, GetSIPAccount_External, SIPRequestAuthenticator.AuthenticateSIPRequest, FireProxyLogEvent, uasTransaction); uasTransaction.NewCallReceived += (local, remote, transaction, request) => { m_callManager.QueueNewCall(outgoingCall); }; uasTransaction.GotRequest(localSIPEndPoint, remoteEndPoint, sipRequest); } else if (GetCanonicalDomain_External(sipRequest.URI.Host, true) != null) { // Call identified as incoming call for application server serviced domain. if (sipRequest.URI.User.IsNullOrBlank()) { // Cannot process incoming call if no user is specified. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "INVITE received with an empty URI user " + sipRequest.URI.ToString() + ", returning address incomplete.", null)); UASInviteTransaction uasTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy); SIPResponse addressIncompleteResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.AddressIncomplete, "No user specified"); uasTransaction.SendFinalResponse(addressIncompleteResponse); } else { // Send the incoming call to the call manager for processing. string uriUser = sipRequest.URI.User; string uriDomain = GetCanonicalDomain_External(sipRequest.URI.Host, true); UASInviteTransaction uasTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy); SIPServerUserAgent incomingCall = new SIPServerUserAgent(m_sipTransport, m_outboundProxy, uriUser, uriDomain, SIPCallDirection.In, GetSIPAccount_External, null, FireProxyLogEvent, uasTransaction); uasTransaction.NewCallReceived += (local, remote, transaction, request) => { m_callManager.QueueNewCall(incomingCall); }; uasTransaction.GotRequest(localSIPEndPoint, remoteEndPoint, sipRequest); } } else { // Return not found for non-serviced domain. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Domain not serviced " + sipRequest.URI.ToString() + ", returning not found.", null)); UASInviteTransaction uasTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy); SIPResponse notServicedResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.NotFound, "Domain not serviced"); uasTransaction.SendFinalResponse(notServicedResponse); } #endregion } else if (sipRequest.Method == SIPMethodsEnum.MESSAGE) { #region Processing non-INVITE requests that are accepted by the dialplan processing engine. if (GetCanonicalDomain_External(sipRequest.Header.From.FromUserField.URI.Host, false) != null) { // Call identified as outgoing request for application server serviced domain. string fromDomain = GetCanonicalDomain_External(sipRequest.Header.From.FromUserField.URI.Host, false); SIPNonInviteTransaction nonInviteTransaction = m_sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy); SIPNonInviteServerUserAgent outgoingRequest = new SIPNonInviteServerUserAgent(m_sipTransport, m_outboundProxy, fromUser, fromDomain, SIPCallDirection.Out, GetSIPAccount_External, SIPRequestAuthenticator.AuthenticateSIPRequest, FireProxyLogEvent, nonInviteTransaction); nonInviteTransaction.NonInviteRequestReceived += (local, remote, transaction, request) => { m_callManager.QueueNewCall(outgoingRequest); }; nonInviteTransaction.GotRequest(localSIPEndPoint, remoteEndPoint, sipRequest); } else if (GetCanonicalDomain_External(sipRequest.URI.Host, true) != null) { // Call identified as incoming call for application server serviced domain. if (sipRequest.URI.User.IsNullOrBlank()) { // Cannot process incoming call if no user is specified. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, sipRequest.Method + " request received with an empty URI user " + sipRequest.URI.ToString() + ", returning address incomplete.", null)); SIPResponse addressIncompleteResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.AddressIncomplete, "No user specified"); m_sipTransport.SendResponse(addressIncompleteResponse); } else { // Send the incoming call to the call manager for processing. string uriUser = sipRequest.URI.User; string uriDomain = GetCanonicalDomain_External(sipRequest.URI.Host, true); SIPNonInviteTransaction nonInviteTransaction = m_sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, m_outboundProxy); SIPNonInviteServerUserAgent incomingRequest = new SIPNonInviteServerUserAgent(m_sipTransport, m_outboundProxy, uriUser, uriDomain, SIPCallDirection.In, GetSIPAccount_External, null, FireProxyLogEvent, nonInviteTransaction); nonInviteTransaction.NonInviteRequestReceived += (local, remote, transaction, request) => { m_callManager.QueueNewCall(incomingRequest); }; nonInviteTransaction.GotRequest(localSIPEndPoint, remoteEndPoint, sipRequest); } } else { // Return not found for non-serviced domain. FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Domain not serviced " + sipRequest.URI.ToString() + ", returning not found.", null)); SIPResponse notServicedResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.NotFound, "Domain not serviced"); m_sipTransport.SendResponse(notServicedResponse); } #endregion } else { FireProxyLogEvent(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.UnrecognisedMessage, "MethodNotAllowed response for " + sipRequest.Method + " from " + fromUser + " socket " + remoteEndPoint.ToString() + ".", null)); SIPResponse notAllowedResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null); m_sipTransport.SendResponse(notAllowedResponse); } } catch (Exception excp) { string reqExcpError = "Exception SIPAppServerCore GotRequest (" + remoteEndPoint + "). " + excp.Message; logger.Error(reqExcpError); SIPMonitorEvent reqExcpEvent = new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.Error, reqExcpError, sipRequest, null, localSIPEndPoint, remoteEndPoint, SIPCallDirection.In); FireProxyLogEvent(reqExcpEvent); throw excp; } }
static void Main() { Console.WriteLine("SIPSorcery call hold example."); Console.WriteLine("Press ctrl-c to exit."); // Plumbing code to facilitate a graceful exit. CancellationTokenSource exitCts = new CancellationTokenSource(); // Cancellation token to stop the SIP transport and RTP stream. bool isCallHungup = false; bool hasCallFailed = false; AddConsoleLogger(); // Set up a default SIP transport. var sipTransport = new SIPTransport(); sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT))); //EnableTraceLogs(sipTransport); // Get the default speaker. var(audioOutEvent, audioOutProvider) = GetAudioOutputDevice(); WaveInEvent waveInEvent = GetAudioInputDevice(); RTPMediaSession RtpMediaSession = null; // Create a client/server user agent to place a call to a remote SIP server along with event handlers for the different stages of the call. var userAgent = new SIPUserAgent(sipTransport, null); userAgent.ClientCallTrying += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Trying: {resp.StatusCode} {resp.ReasonPhrase}."); userAgent.ClientCallRinging += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Ringing: {resp.StatusCode} {resp.ReasonPhrase}."); userAgent.ClientCallFailed += (uac, err) => { Log.LogWarning($"{uac.CallDescriptor.To} Failed: {err}"); hasCallFailed = true; exitCts.Cancel(); }; userAgent.ClientCallAnswered += (uac, resp) => { if (resp.Status == SIPResponseStatusCodesEnum.Ok) { Log.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}."); PlayRemoteMedia(RtpMediaSession, audioOutProvider); } else { Log.LogWarning($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}."); hasCallFailed = true; exitCts.Cancel(); } }; userAgent.OnCallHungup += () => { Log.LogInformation($"Call hungup by remote party."); exitCts.Cancel(); }; userAgent.ServerCallCancelled += (uas) => Log.LogInformation("Incoming call cancelled by caller."); sipTransport.SIPTransportRequestReceived += async(localEndPoint, remoteEndPoint, sipRequest) => { if (sipRequest.Header.From != null && sipRequest.Header.From.FromTag != null && sipRequest.Header.To != null && sipRequest.Header.To.ToTag != null) { // This is an in-dialog request that will be handled directly by a user agent instance. } else if (sipRequest.Method == SIPMethodsEnum.INVITE) { if (userAgent?.IsCallActive == true) { Log.LogWarning($"Busy response returned for incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); // If we are already on a call return a busy response. UASInviteTransaction uasTransaction = new UASInviteTransaction(sipTransport, sipRequest, null); SIPResponse busyResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.BusyHere, null); uasTransaction.SendFinalResponse(busyResponse); } else { Log.LogInformation($"Incoming call request from {remoteEndPoint}: {sipRequest.StatusLine}."); var incomingCall = userAgent.AcceptCall(sipRequest); RtpMediaSession = new RTPMediaSession(SDPMediaTypesEnum.audio, (int)SDPMediaFormatsEnum.PCMU, AddressFamily.InterNetwork); RtpMediaSession.RemotePutOnHold += () => Log.LogInformation("Remote call party has placed us on hold."); RtpMediaSession.RemoteTookOffHold += () => Log.LogInformation("Remote call party took us off hold."); await userAgent.Answer(incomingCall, RtpMediaSession); PlayRemoteMedia(RtpMediaSession, audioOutProvider); waveInEvent.StartRecording(); Log.LogInformation($"Answered incoming call from {sipRequest.Header.From.FriendlyDescription()} at {remoteEndPoint}."); } } else { Log.LogDebug($"SIP {sipRequest.Method} request received but no processing has been set up for it, rejecting."); SIPResponse notAllowedResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null); await sipTransport.SendResponseAsync(notAllowedResponse); } }; // Wire up the RTP send session to the audio output device. uint rtpSendTimestamp = 0; waveInEvent.DataAvailable += (object sender, WaveInEventArgs args) => { byte[] sample = new byte[args.Buffer.Length / 2]; int sampleIndex = 0; for (int index = 0; index < args.BytesRecorded; index += 2) { var ulawByte = NAudio.Codecs.MuLawEncoder.LinearToMuLawSample(BitConverter.ToInt16(args.Buffer, index)); sample[sampleIndex++] = ulawByte; } if (RtpMediaSession != null) { RtpMediaSession.SendAudioFrame(rtpSendTimestamp, sample); rtpSendTimestamp += (uint)(8000 / waveInEvent.BufferMilliseconds); } }; // At this point the call has been initiated and everything will be handled in an event handler. Task.Run(async() => { try { while (!exitCts.Token.WaitHandle.WaitOne(0)) { var keyProps = Console.ReadKey(); if (keyProps.KeyChar == 'c') { if (!userAgent.IsCallActive) { RtpMediaSession = new RTPMediaSession(SDPMediaTypesEnum.audio, (int)SDPMediaFormatsEnum.PCMU, AddressFamily.InterNetwork); RtpMediaSession.RemotePutOnHold += () => Log.LogInformation("Remote call party has placed us on hold."); RtpMediaSession.RemoteTookOffHold += () => Log.LogInformation("Remote call party took us off hold."); var callDescriptor = GetCallDescriptor(DEFAULT_DESTINATION_SIP_URI); await userAgent.InitiateCall(callDescriptor, RtpMediaSession); } else { Log.LogWarning("There is already an active call."); } } else if (keyProps.KeyChar == 'h') { // Place call on/off hold. if (userAgent.IsCallActive) { if (RtpMediaSession.LocalOnHold) { Log.LogInformation("Taking the remote call party off hold."); RtpMediaSession.TakeOffHold(); } else { Log.LogInformation("Placing the remote call party on hold."); RtpMediaSession.PutOnHold(); } } else { Log.LogWarning("There is no active call to put on hold."); } } else if (keyProps.KeyChar == 't') { if (userAgent.IsCallActive) { var transferURI = SIPURI.ParseSIPURI(TRANSFER_DESTINATION_SIP_URI); bool result = await userAgent.BlindTransfer(transferURI, TimeSpan.FromSeconds(TRANSFER_TIMEOUT_SECONDS), exitCts.Token); if (result) { // If the transfer was accepted the original call will already have been hungup. // Wait a second for the transfer NOTIFY request to arrive. await Task.Delay(1000); exitCts.Cancel(); } else { Log.LogWarning($"Transfer to {TRANSFER_DESTINATION_SIP_URI} failed."); } } else { Log.LogWarning("There is no active call to transfer."); } } else if (keyProps.KeyChar == 'q') { // Quit application. exitCts.Cancel(); } } } catch (Exception excp) { SIPSorcery.Sys.Log.Logger.LogError($"Exception Key Press listener. {excp.Message}."); } }); // Ctrl-c will gracefully exit the call at any point. Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; exitCts.Cancel(); }; // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed. exitCts.Token.WaitHandle.WaitOne(); #region Cleanup. Log.LogInformation("Exiting..."); RtpMediaSession?.Close(); waveInEvent?.StopRecording(); audioOutEvent?.Stop(); if (!isCallHungup && userAgent != null) { if (userAgent.IsCallActive) { Log.LogInformation($"Hanging up call to {userAgent?.CallDescriptor?.To}."); userAgent.Hangup(); } else if (!hasCallFailed) { Log.LogInformation($"Cancelling call to {userAgent?.CallDescriptor?.To}."); userAgent.Cancel(); } // Give the BYE or CANCEL request time to be transmitted. Log.LogInformation("Waiting 1s for call to clean up..."); Task.Delay(1000).Wait(); } SIPSorcery.Net.DNSManager.Stop(); if (sipTransport != null) { Log.LogInformation("Shutting down SIP transport..."); sipTransport.Shutdown(); } #endregion }
/// <summary> /// Handler for when an in dialog request is received on an established call. /// Typical types of request will be re-INVITES for things like putting a call on or /// off hold and REFER requests for transfers. Some in dialog request types, such /// as re-INVITES have specific events so they can be bubbled up to the /// application to deal with. /// </summary> /// <param name="sipRequest">The in dialog request received.</param> private async Task DialogRequestReceivedAsync(SIPRequest sipRequest) { if (sipRequest.Method == SIPMethodsEnum.BYE) { logger.LogInformation($"Remote call party hungup {sipRequest.StatusLine}."); Dialogue.DialogueState = SIPDialogueStateEnum.Terminated; SIPNonInviteTransaction byeTx = new SIPNonInviteTransaction(m_transport, sipRequest, null); byeTx.SendResponse(SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null)); CallEnded(); } else if (sipRequest.Method == SIPMethodsEnum.INVITE) { logger.LogDebug($"Re-INVITE request received {sipRequest.StatusLine}."); UASInviteTransaction reInviteTransaction = new UASInviteTransaction(m_transport, sipRequest, m_outboundProxy); try { MediaSession.setRemoteDescription(new RTCSessionDescription { sdp = SDP.ParseSDPDescription(sipRequest.Body), type = RTCSdpType.offer }); CheckRemotePartyHoldCondition(MediaSession.remoteDescription.sdp); var answerSdp = await MediaSession.createAnswer(null).ConfigureAwait(false); Dialogue.RemoteSDP = sipRequest.Body; Dialogue.SDP = answerSdp.ToString(); Dialogue.RemoteCSeq = sipRequest.Header.CSeq; var okResponse = reInviteTransaction.GetOkResponse(SDP.SDP_MIME_CONTENTTYPE, Dialogue.SDP); reInviteTransaction.SendFinalResponse(okResponse); } catch (Exception ex) { logger.LogError(ex, "MediaSession can't process the re-INVITE request."); if (OnReinviteRequest == null) { // The application isn't prepared to accept re-INVITE requests and we can't work out what it was for. // We'll reject as gently as we can to try and not lose the call. SIPResponse notAcceptableResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.NotAcceptable, null); reInviteTransaction.SendFinalResponse(notAcceptableResponse); } else { // The application is going to handle the re-INVITE request. We'll send a Trying response as a precursor. SIPResponse tryingResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Trying, null); await reInviteTransaction.SendProvisionalResponse(tryingResponse).ConfigureAwait(false); OnReinviteRequest.Invoke(reInviteTransaction); } } } else if (sipRequest.Method == SIPMethodsEnum.OPTIONS) { //Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "OPTIONS request for established dialogue " + dialogue.DialogueName + ".", dialogue.Owner)); SIPResponse okResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); okResponse.Body = Dialogue.RemoteSDP; okResponse.Header.ContentLength = okResponse.Body.Length; okResponse.Header.ContentType = m_sdpContentType; await SendResponseAsync(okResponse).ConfigureAwait(false); } else if (sipRequest.Method == SIPMethodsEnum.MESSAGE) { //Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "MESSAGE for call " + sipRequest.URI.ToString() + ": " + sipRequest.Body + ".", dialogue.Owner)); SIPResponse okResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); await m_transport.SendResponseAsync(okResponse).ConfigureAwait(false); } else if (sipRequest.Method == SIPMethodsEnum.REFER) { if (sipRequest.Header.ReferTo.IsNullOrBlank()) { // A REFER request must have a Refer-To header. //Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Bad REFER request, no Refer-To header.", dialogue.Owner)); SIPResponse invalidResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.BadRequest, "Missing mandatory Refer-To header"); await SendResponseAsync(invalidResponse).ConfigureAwait(false); } else { //TODO: Add handling logic for in transfer requests from the remote call party. } } else if (sipRequest.Method == SIPMethodsEnum.NOTIFY) { SIPResponse okResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); await SendResponseAsync(okResponse).ConfigureAwait(false); if (sipRequest.Body?.Length > 0 && sipRequest.Header.ContentType?.Contains(m_sipReferContentType) == true) { OnTransferNotify?.Invoke(sipRequest.Body); } } }
/// <summary> /// Handler for when an in dialog request is received on an established call. /// Typical types of request will be re-INVITES for things like putting a call on or /// off hold and REFER requests for transfers. Some in dialog request types, such /// as re-INVITES have specific events so they can be bubbled up to the /// application to deal with. /// </summary> /// <param name="request">The in dialog request received.</param> public async Task InDialogRequestReceivedAsync(SIPRequest sipRequest) { // Make sure the request matches our dialog and is not a stray. // A dialog request should match on to tag, from tag and call ID. We'll be more // accepting just in case the sender got the tags wrong. if (Dialogue == null || sipRequest.Header.CallId != Dialogue.CallId) { var noCallLegResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist, null); var sendResult = await SendResponse(noCallLegResponse); if (sendResult != SocketError.Success) { logger.LogWarning($"SIPUserAgent send response failed in InCallRequestReceivedAsync with {sendResult}."); } } else { if (sipRequest.Method == SIPMethodsEnum.BYE) { logger.LogDebug($"Matching dialogue found for {sipRequest.StatusLine}."); SIPNonInviteTransaction byeTransaction = m_transport.CreateNonInviteTransaction(sipRequest, m_outboundProxy); SIPResponse byeResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); byeTransaction.SendFinalResponse(byeResponse); CallHungup?.Invoke(); m_uac = null; m_uas = null; } else if (sipRequest.Method == SIPMethodsEnum.INVITE) { logger.LogDebug($"Re-INVITE request received {sipRequest.StatusLine}."); UASInviteTransaction reInviteTransaction = m_transport.CreateUASTransaction(sipRequest, m_outboundProxy); if (OnReinviteRequest == null) { // The application isn't prepared to accept re-INVITE requests. We'll reject as gently as we can to try and not lose the call. SIPResponse notAcceptableResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.NotAcceptable, null); reInviteTransaction.SendFinalResponse(notAcceptableResponse); } else { SIPResponse tryingResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Trying, null); reInviteTransaction.SendProvisionalResponse(tryingResponse); OnReinviteRequest(reInviteTransaction); } } else if (sipRequest.Method == SIPMethodsEnum.OPTIONS) { //Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "OPTIONS request for established dialogue " + dialogue.DialogueName + ".", dialogue.Owner)); SIPNonInviteTransaction optionsTransaction = m_transport.CreateNonInviteTransaction(sipRequest, m_outboundProxy); SIPResponse okResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); okResponse.Body = Dialogue.RemoteSDP; okResponse.Header.ContentLength = okResponse.Body.Length; okResponse.Header.ContentType = m_sdpContentType; optionsTransaction.SendFinalResponse(okResponse); } else if (sipRequest.Method == SIPMethodsEnum.MESSAGE) { //Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "MESSAGE for call " + sipRequest.URI.ToString() + ": " + sipRequest.Body + ".", dialogue.Owner)); SIPNonInviteTransaction messageTransaction = m_transport.CreateNonInviteTransaction(sipRequest, m_outboundProxy); SIPResponse okResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null); messageTransaction.SendFinalResponse(okResponse); } else if (sipRequest.Method == SIPMethodsEnum.REFER) { //Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "REFER received on dialogue " + dialogue.DialogueName + ", transfer mode is " + dialogue.TransferMode + ".", dialogue.Owner)); SIPNonInviteTransaction referTransaction = m_transport.CreateNonInviteTransaction(sipRequest, m_outboundProxy); if (sipRequest.Header.ReferTo.IsNullOrBlank()) { // A REFER request must have a Refer-To header. //Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "Bad REFER request, no Refer-To header.", dialogue.Owner)); SIPResponse invalidResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.BadRequest, "Missing mandatory Refer-To header"); referTransaction.SendFinalResponse(invalidResponse); } else { //TODO: Add handling logic for indialog REFER requests. } } } }