Example #1
0
        private static void SIPTransportRequestReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest)
        {
            if (sipRequest.Method == SIPMethodsEnum.INVITE)
            {
                Console.WriteLine("INVITE received from  " + localSIPEndPoint.ToString());
                IPEndPoint sipPhoneEndPoint = SDP.GetSDPRTPEndPoint(sipRequest.Body);

                UASInviteTransaction uasTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, null);
                SIPServerUserAgent   uas            = new SIPServerUserAgent(m_sipTransport, null, null, null, SIPCallDirection.In, null, null, null, uasTransaction);

                SIPResponse tryingResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Trying, null);
                uasTransaction.SendInformationalResponse(tryingResponse);

                if (m_xmppClient == null)
                {
                    m_xmppClient = new XMPPClient(XMPP_SERVER, XMPP_SERVER_PORT, XMPP_REALM, m_xmppUsername, m_xmppPassword);
                    m_xmppClient.Disconnected += XMPPDisconnected;
                    m_xmppClient.IsBound      += () => { XMPPPlaceCall(uas); };
                    ThreadPool.QueueUserWorkItem(delegate { m_xmppClient.Connect(); });
                }
                else
                {
                    XMPPPlaceCall(uas);
                }
            }
            else if (sipRequest.Method == SIPMethodsEnum.CANCEL)
            {
                UASInviteTransaction inviteTransaction = (UASInviteTransaction)m_sipTransport.GetTransaction(SIPTransaction.GetRequestTransactionId(sipRequest.Header.Vias.TopViaHeader.Branch, SIPMethodsEnum.INVITE));

                if (inviteTransaction != null)
                {
                    Console.WriteLine("Matching CANCEL request received " + sipRequest.URI.ToString() + ".");
                    SIPCancelTransaction cancelTransaction = m_sipTransport.CreateCancelTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, inviteTransaction);
                    cancelTransaction.GotRequest(localSIPEndPoint, remoteEndPoint, sipRequest);
                }
                else
                {
                    Console.WriteLine("No matching transaction was found for CANCEL to " + sipRequest.URI.ToString() + ".");
                    SIPResponse noCallLegResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist, null);
                    m_sipTransport.SendResponse(noCallLegResponse);
                }
            }
            else if (sipRequest.Method == SIPMethodsEnum.BYE)
            {
                Console.WriteLine("BYE request received.");

                if (m_activeCalls.ContainsKey(sipRequest.Header.CallId))
                {
                    SIPResponse okResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                    m_sipTransport.SendResponse(okResponse);
                    m_activeCalls[sipRequest.Header.CallId].TerminateXMPPCall();
                    m_activeCalls.Remove(sipRequest.Header.CallId);
                }
                else
                {
                    SIPResponse doesntExistResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist, null);
                    m_sipTransport.SendResponse(doesntExistResponse);
                }
            }
        }
Example #2
0
        /// <summary>
        /// Sets relevant properties for this session based on the SDP from the remote party.
        /// </summary>
        /// <param name="sdp">The SDP from the remote call party.</param>
        public void SetRemoteSDP(SDP sdp)
        {
            RemoteSDP = sdp;

            foreach (var announcement in sdp.Media.Where(x => x.Media == SDPMediaTypesEnum.audio))
            {
                foreach (var mediaFormat in announcement.MediaFormats)
                {
                    if (mediaFormat.FormatAttribute?.StartsWith(TELEPHONE_EVENT_ATTRIBUTE) == true)
                    {
                        if (!int.TryParse(mediaFormat.FormatID, out var remoteRtpEventPayloadID))
                        {
                            logger.LogWarning("The media format on the telephone event attribute was not a valid integer.");
                        }
                        else
                        {
                            base.RemoteRtpEventPayloadID = remoteRtpEventPayloadID;
                        }
                        break;
                    }
                }
            }
            var dstEndPoint = sdp.GetSDPRTPEndPoint();

            SetDestination(dstEndPoint, new IPEndPoint(dstEndPoint.Address, dstEndPoint.Port + 1));
        }
        public static string MangleSDP(string sdpBody, string publicIPAddress, out bool wasMangled)
        {
            wasMangled = false;

            try
            {
                if (sdpBody != null && publicIPAddress != null)
                {
                    string sdpAddress = SDP.GetSDPRTPEndPoint(sdpBody).Address.ToString();

                    // Only mangle if there is something to change. For example the server could be on the same private subnet in which case it can't help.
                    if (IPSocket.IsPrivateAddress(sdpAddress) && publicIPAddress != sdpAddress)
                    {
                        //logger.LogDebug("MangleSDP replacing private " + sdpAddress + " with " + publicIPAddress + ".");
                        string mangledSDP = Regex.Replace(sdpBody, @"c=IN IP4 (?<ipaddress>(\d+\.){3}\d+)", "c=IN IP4 " + publicIPAddress, RegexOptions.Singleline);
                        wasMangled = true;

                        return(mangledSDP);
                    }
                }
                else
                {
                    logger.LogWarning("Mangle SDP was called with an empty body or public IP address.");
                }

                return(sdpBody);
            }
            catch (Exception excp)
            {
                logger.LogError("Exception MangleSDP. " + excp.Message);
                return(sdpBody);
            }
        }
Example #4
0
        private void Answered(SDP xmppSDP)
        {
            //Console.WriteLine("Yay call answered.");
            //Console.WriteLine(sdp.ToString());
            m_xmppServerEndPoint = SDP.GetSDPRTPEndPoint(xmppSDP.ToString());
            logger.Debug("Sending STUN binding request to " + m_xmppServerEndPoint + ".");
            STUNMessage initMessage = new STUNMessage(STUNMessageTypesEnum.BindingRequest);

            initMessage.AddUsernameAttribute(xmppSDP.IceUfrag + m_localSTUNUFrag);
            byte[] stunMessageBytes = initMessage.ToByteBuffer();
            m_xmppMediaSocket.Send(stunMessageBytes, stunMessageBytes.Length, m_xmppServerEndPoint);

            m_uas.Answer("application/sdp", GetSDPForSIPResponse().ToString(), null, SIPDialogueTransferModesEnum.NotAllowed);
        }
        // private static ILogger logger = Log.Logger;

        public static string MangleSDP(string sdpBody, string publicIPAddress, out bool wasMangled)
        {
            wasMangled = false;

            try
            {
                if (sdpBody != null && publicIPAddress != null)
                {
                    IPAddress addr = SDP.GetSDPRTPEndPoint(sdpBody).Address;
                    //rj2: need to consider publicAddress and IPv6 for mangling
                    IPAddress pubaddr    = IPAddress.Parse(publicIPAddress);
                    string    sdpAddress = addr.ToString();

                    // Only mangle if there is something to change. For example the server could be on the same private subnet in which case it can't help.
                    if (IPSocket.IsPrivateAddress(sdpAddress) && publicIPAddress != sdpAddress &&
                        pubaddr.AddressFamily == AddressFamily.InterNetworkV6 &&
                        addr.AddressFamily == AddressFamily.InterNetworkV6)
                    {
                        string mangledSDP = Regex.Replace(sdpBody, @"c=IN IP6 (?<ipaddress>([:a-fA-F0-9]+))",
                                                          "c=IN IP6" + publicIPAddress, RegexOptions.Singleline);
                        wasMangled = true;

                        return(mangledSDP);
                    }
                    else if (IPSocket.IsPrivateAddress(sdpAddress) && publicIPAddress != sdpAddress &&
                             pubaddr.AddressFamily ==
                             AddressFamily.InterNetwork &&
                             addr.AddressFamily == AddressFamily.InterNetwork)
                    {
                        //logger.LogDebug("MangleSDP replacing private " + sdpAddress + " with " + publicIPAddress + ".");
                        string mangledSDP = Regex.Replace(sdpBody, @"c=IN IP4 (?<ipaddress>(\d+\.){3}\d+)",
                                                          "c=IN IP4 " + publicIPAddress, RegexOptions.Singleline);
                        wasMangled = true;

                        return(mangledSDP);
                    }
                }
                else
                {
                    Logger.Logger.Warn("Mangle SDP was called with an empty body or public IP address.");
                }

                return(sdpBody);
            }
            catch (Exception excp)
            {
                Logger.Logger.Error("Exception MangleSDP. ->" + excp.Message);
                return(sdpBody);
            }
        }
Example #6
0
        /// <summary>
        /// An outgoing call was successfully answered.
        /// </summary>
        /// <param name="uac">The local SIP user agent client that initiated the call.</param>
        /// <param name="sipResponse">The SIP answer response received from the remote party.</param>
        private void CallAnswered(ISIPClientUserAgent uac, SIPResponse sipResponse)
        {
            StatusMessage("Call answered: " + sipResponse.StatusCode + " " + sipResponse.ReasonPhrase + ".");

            if (sipResponse.StatusCode >= 200 && sipResponse.StatusCode <= 299)
            {
                IPEndPoint remoteSDPEndPoint = SDP.GetSDPRTPEndPoint(sipResponse.Body);
                _audioChannel.SetRemoteRTPEndPoint(remoteSDPEndPoint);
                CallAnswer();
            }
            else
            {
                CallFinished();
            }
        }
Example #7
0
        /// <summary>
        /// Event handler for an answer on an outgoing Google Voice call.
        /// </summary>
        /// <param name="xmppSDP">The SDP packet received from the Google Voice gateway.</param>
        private void XMPPAnswered(SDP xmppSDP)
        {
            StatusMessage("Google Voice call answered.");

            IPEndPoint remoteSDPEndPoint = SDP.GetSDPRTPEndPoint(xmppSDP.ToString());

            _audioChannel.SetRemoteRTPEndPoint(remoteSDPEndPoint);

            // Google Voice require that a STUN exchange occurs on the RTP socket before the RTP packet can flow.
            // This code block sends a STUN binding request to the Google Voice gateway.
            STUNMessage initMessage = new STUNMessage(STUNMessageTypesEnum.BindingRequest);

            initMessage.AddUsernameAttribute(xmppSDP.IceUfrag + m_localSTUNUFrag);
            byte[] stunMessageBytes = initMessage.ToByteBuffer();
            //_audioChannel.SendRTPRaw(stunMessageBytes, stunMessageBytes.Length);
        }
Example #8
0
        public void Call(SIPCallDescriptor sipCallDescriptor)
        {
            CallDescriptor         = sipCallDescriptor;
            m_xmppClient           = new XMPPClient("talk.google.com", 5222, "google.com", null, null);
            m_xmppClient.IsBound  += IsBound;
            m_xmppClient.Answered += Answered;

            IPEndPoint sdpEndPoint = SDP.GetSDPRTPEndPoint(CallDescriptor.Content);

            if (IPSocket.IsPrivateAddress(sdpEndPoint.Address.ToString()))
            {
                bool wasSDPMangled;
                CallDescriptor.Content = SIPPacketMangler.MangleSDP(CallDescriptor.Content, CallDescriptor.MangleIPAddress.ToString(), out wasSDPMangled);
            }

            ThreadPool.QueueUserWorkItem(delegate { m_xmppClient.Connect(); });
        }
Example #9
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);
            }
        }
Example #10
0
        public void Call(SIPCallDescriptor descriptor)
        {
            try
            {
                CallDescriptor = descriptor;
                SIPURI destinationURI = SIPURI.ParseSIPURIRelaxed(descriptor.Uri);

                bool       wasSDPMangled = false;
                IPEndPoint sdpEndPoint   = null;
                if (descriptor.MangleIPAddress != null)
                {
                    sdpEndPoint = SDP.GetSDPRTPEndPoint(descriptor.Content);
                    if (sdpEndPoint != null)
                    {
                        descriptor.Content = SIPPacketMangler.MangleSDP(descriptor.Content, descriptor.MangleIPAddress.ToString(), out wasSDPMangled);
                    }
                }

                if (wasSDPMangled)
                {
                    Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "SDP on Google Voice call had RTP socket mangled from " + sdpEndPoint.ToString() + " to " + descriptor.MangleIPAddress.ToString() + ":" + sdpEndPoint.Port + ".", Owner));
                }
                else if (sdpEndPoint != null)
                {
                    Log_External(new SIPMonitorConsoleEvent(SIPMonitorServerTypesEnum.AppServer, SIPMonitorEventTypesEnum.DialPlan, "SDP on Google Voice call could not be mangled, using original RTP socket of " + sdpEndPoint.ToString() + ".", Owner));
                }

                SIPDialogue = m_googleVoiceCall.InitiateCall(descriptor.Username, descriptor.Password, descriptor.CallbackNumber, destinationURI.User, descriptor.CallbackPattern, descriptor.CallbackPhoneType, MAX_CALLBACK_WAIT_TIME, descriptor.ContentType, descriptor.Content);

                if (SIPDialogue != null)
                {
                    CallAnswered(this, null);
                }
                else
                {
                    CallFailed(this, "Google Voice call failed.");
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception GoogleVoiceCallAgent Call. " + excp.Message);
                CallFailed(this, excp.Message);
            }
        }
Example #11
0
        public void GetFirstMedaiOfferIPv6RTPSocketUnitTest()
        {
            Logger.LogDebug(System.Reflection.MethodBase.GetCurrentMethod().Name);

            string sdpStr = "v=0" + m_CRLF +
                            "o=nasa1 971731711378798081 0 IN IP6 2201:056D::112E:144A:1E24" + m_CRLF +
                            "s=(Almost) live video feed from Mars-II satellite" + m_CRLF +
                            "p=+1 713 555 1234" + m_CRLF +
                            "c=IN IP6 FF1E:03AD::7F2E:172A:1E24" + m_CRLF +
                            "t=3338481189 3370017201" + m_CRLF +
                            "m=audio 6000 RTP/AVP 2" + m_CRLF +
                            "a=rtpmap:2 G726-32/8000" + m_CRLF +
                            "m=video 6024 RTP/AVP 107" + m_CRLF +
                            "a=rtpmap:107 H263-1998/90000";

            IPEndPoint audioRtpEndPoint = SDP.GetSDPRTPEndPoint(sdpStr);

            Assert.True(audioRtpEndPoint.Address.Equals(IPAddress.Parse("FF1E:03AD::7F2E:172A:1E24")), "The media RTP address was not correct.");
            Assert.True(audioRtpEndPoint.Port == 6000, "The media RTP port was not correct.");
        }
Example #12
0
        public void GetFirstMedaiOfferRTPSocketUnitTest()
        {
            Logger.LogDebug(System.Reflection.MethodBase.GetCurrentMethod().Name);

            string sdpStr =
                "v=0" + m_CRLF +
                "o=root 3285 3285 IN IP4 10.0.0.4" + m_CRLF +
                "s=session" + m_CRLF +
                "c=IN IP4 10.0.0.4" + m_CRLF +
                "t=0 0" + m_CRLF +
                "m=audio 12228 RTP/AVP 0 101" + m_CRLF +
                "a=rtpmap:0 PCMU/8000" + m_CRLF +
                "a=rtpmap:101 telephone-event/8000" + m_CRLF +
                "a=fmtp:101 0-16" + m_CRLF +
                "a=silenceSupp:off - - - -" + m_CRLF +
                "a=ptime:20" + m_CRLF +
                "a=sendrecv";

            IPEndPoint audioRtpEndPoint = SDP.GetSDPRTPEndPoint(sdpStr);

            Assert.True(audioRtpEndPoint.Address.Equals(IPAddress.Parse("10.0.0.4")), "The media RTP address was not correct.");
            Assert.True(audioRtpEndPoint.Port == 12228, "The media RTP port was not correct.");
        }
Example #13
0
        private static readonly int RTP_REPORTING_PERIOD_SECONDS = 5;       // Period at which to write RTP stats.

        static void Main()
        {
            Console.WriteLine("SIPSorcery client user agent server example.");
            Console.WriteLine("Press ctrl-c to exit.");

            // Logging configuration. Can be ommitted if internal SIPSorcery debug and warning messages are not required.
            var loggerFactory = new Microsoft.Extensions.Logging.LoggerFactory();
            var loggerConfig  = new LoggerConfiguration()
                                .Enrich.FromLogContext()
                                .MinimumLevel.Is(Serilog.Events.LogEventLevel.Debug)
                                .WriteTo.Console()
                                .CreateLogger();

            loggerFactory.AddSerilog(loggerConfig);
            SIPSorcery.Sys.Log.LoggerFactory = loggerFactory;

            // Set up a default SIP transport.
            IPAddress defaultAddr  = LocalIPConfig.GetDefaultIPv4Address();
            var       sipTransport = new SIPTransport(SIPDNSManager.ResolveSIPService, new SIPTransactionEngine());
            int       port         = FreePort.FindNextAvailableUDPPort(SIPConstants.DEFAULT_SIP_PORT);
            var       sipChannel   = new SIPUDPChannel(new IPEndPoint(defaultAddr, port));

            sipTransport.AddSIPChannel(sipChannel);

            // To keep things a bit simpler this example only supports a single call at a time and the SIP server user agent
            // acts as a singleton
            SIPServerUserAgent      uas    = null;
            CancellationTokenSource uasCts = null;

            // Because this is a server user agent the SIP transport must start listening for client user agents.
            sipTransport.SIPTransportRequestReceived += (SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) =>
            {
                if (sipRequest.Method == SIPMethodsEnum.INVITE)
                {
                    SIPSorcery.Sys.Log.Logger.LogInformation("Incoming call request: " + localSIPEndPoint + "<-" + remoteEndPoint + " " + sipRequest.URI.ToString() + ".");

                    // If there's already a call in progress hang it up. Of course this is not ideal for a real softphone or server but it
                    // means this example can be kept a little it simpler.
                    uas?.Hangup();

                    UASInviteTransaction uasTransaction = sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, null);
                    uas    = new SIPServerUserAgent(sipTransport, null, null, null, SIPCallDirection.In, null, null, null, uasTransaction);
                    uasCts = new CancellationTokenSource();

                    uas.Progress(SIPResponseStatusCodesEnum.Trying, null, null, null, null);
                    uas.Progress(SIPResponseStatusCodesEnum.Ringing, null, null, null, null);

                    // Initialise an RTP session to receive the RTP packets from the remote SIP server.
                    Socket rtpSocket     = null;
                    Socket controlSocket = null;
                    NetServices.CreateRtpSocket(defaultAddr, 49000, 49100, false, out rtpSocket, out controlSocket);

                    IPEndPoint rtpEndPoint    = rtpSocket.LocalEndPoint as IPEndPoint;
                    IPEndPoint dstRtpEndPoint = SDP.GetSDPRTPEndPoint(sipRequest.Body);
                    var        rtpSession     = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);

                    var rtpTask = Task.Run(() => SendRecvRtp(rtpSocket, rtpSession, dstRtpEndPoint, AUDIO_FILE, uasCts))
                                  .ContinueWith(_ => { if (uas?.IsHungup == false)
                                                       {
                                                           uas?.Hangup();
                                                       }
                                                });

                    uas.Answer(SDP.SDP_MIME_CONTENTTYPE, GetSDP(rtpEndPoint).ToString(), null, SIPDialogueTransferModesEnum.NotAllowed);
                }
                else if (sipRequest.Method == SIPMethodsEnum.BYE)
                {
                    SIPSorcery.Sys.Log.Logger.LogInformation("Call hungup.");
                    SIPNonInviteTransaction byeTransaction = sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, null);
                    SIPResponse             byeResponse    = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                    byeTransaction.SendFinalResponse(byeResponse);
                    uas?.Hangup();
                    uasCts?.Cancel();
                }
            };

            Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
            {
                e.Cancel = true;

                SIPSorcery.Sys.Log.Logger.LogInformation("Exiting...");
                if (uas?.IsHungup == false)
                {
                    uas?.Hangup();
                }
                uasCts?.Cancel();

                if (sipTransport != null)
                {
                    SIPSorcery.Sys.Log.Logger.LogInformation("Shutting down SIP transport...");
                    sipTransport.Shutdown();
                }
            };
        }
Example #14
0
        private static int INPUT_SAMPLE_PERIOD_MILLISECONDS = 20;            // This sets the frequency of the RTP packets.

        static void Main(string[] args)
        {
            Console.WriteLine("SIPSorcery client user agent example.");
            Console.WriteLine("Press ctrl-c to exit.");

            // Plumbing code to facilitate a graceful exit.
            ManualResetEvent exitMre       = new ManualResetEvent(false);
            bool             isCallHungup  = false;
            bool             hasCallFailed = false;

            AddConsoleLogger();

            SIPURI callUri = SIPURI.ParseSIPURI(DEFAULT_DESTINATION_SIP_URI);

            if (args != null && args.Length > 0)
            {
                if (!SIPURI.TryParse(args[0], out callUri))
                {
                    Log.LogWarning($"Command line argument could not be parsed as a SIP URI {args[0]}");
                }
            }
            Log.LogInformation($"Call destination {callUri}.");

            // Set up a default SIP transport.
            var sipTransport = new SIPTransport();

            EnableTraceLogs(sipTransport);

            // Get the IP address the RTP will be sent from. While we can listen on IPAddress.Any | IPv6Any
            // we can't put 0.0.0.0 or [::0] in the SDP or the callee will ignore us.
            var lookupResult = SIPDNSManager.ResolveSIPService(callUri, false);

            Log.LogDebug($"DNS lookup result for {callUri}: {lookupResult?.GetSIPEndPoint()}.");
            var       dstAddress     = lookupResult.GetSIPEndPoint().Address;
            IPAddress localIPAddress = NetServices.GetLocalAddressForRemote(dstAddress);

            // Initialise an RTP session to receive the RTP packets from the remote SIP server.
            var rtpSession = new RTPMediaSession((int)SDPMediaFormatsEnum.PCMU, localIPAddress.AddressFamily);
            var offerSDP   = rtpSession.GetSDP(localIPAddress);

            // Get the audio input device.
            WaveInEvent waveInEvent = GetAudioInputDevice();

            // Create a client user agent to place a call to a remote SIP server along with event handlers for the different stages of the call.
            var uac = new SIPClientUserAgent(sipTransport);

            uac.CallTrying  += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Trying: {resp.StatusCode} {resp.ReasonPhrase}.");
            uac.CallRinging += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Ringing: {resp.StatusCode} {resp.ReasonPhrase}.");
            uac.CallFailed  += (uac, err) =>
            {
                Log.LogWarning($"{uac.CallDescriptor.To} Failed: {err}");
                hasCallFailed = true;
            };
            uac.CallAnswered += (uac, resp) =>
            {
                if (resp.Status == SIPResponseStatusCodesEnum.Ok)
                {
                    Log.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");

                    // Only set the remote RTP end point if there hasn't already been a packet received on it.
                    if (rtpSession.DestinationEndPoint == null)
                    {
                        rtpSession.DestinationEndPoint = SDP.GetSDPRTPEndPoint(resp.Body);
                        Log.LogDebug($"Remote RTP socket {rtpSession.DestinationEndPoint}.");
                    }

                    rtpSession.SetRemoteSDP(SDP.ParseSDPDescription(resp.Body));
                    waveInEvent.StartRecording();
                }
                else
                {
                    Log.LogWarning($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");
                }
            };

            // The only incoming request that needs to be explicitly handled for this example is if the remote end hangs up the call.
            sipTransport.SIPTransportRequestReceived += async(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) =>
            {
                if (sipRequest.Method == SIPMethodsEnum.BYE)
                {
                    SIPResponse okResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                    await sipTransport.SendResponseAsync(okResponse);

                    if (uac.IsUACAnswered)
                    {
                        Log.LogInformation("Call was hungup by remote server.");
                        isCallHungup = true;
                        exitMre.Set();
                    }
                }
            };

            // Wire up the RTP receive session to the audio output device.
            var(audioOutEvent, audioOutProvider) = GetAudioOutputDevice();
            rtpSession.OnReceivedSampleReady    += (sample) =>
            {
                for (int index = 0; index < sample.Length; index++)
                {
                    short  pcm       = NAudio.Codecs.MuLawDecoder.MuLawToLinearSample(sample[index]);
                    byte[] pcmSample = new byte[] { (byte)(pcm & 0xFF), (byte)(pcm >> 8) };
                    audioOutProvider.AddSamples(pcmSample, 0, 2);
                }
            };

            // 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 (rtpSession.DestinationEndPoint != null)
                {
                    rtpSession.SendAudioFrame(rtpSendTimestamp, sample);
                    rtpSendTimestamp += (uint)(8000 / waveInEvent.BufferMilliseconds);
                }
            };

            // Start the thread that places the call.
            SIPCallDescriptor callDescriptor = new SIPCallDescriptor(
                SIPConstants.SIP_DEFAULT_USERNAME,
                null,
                callUri.ToString(),
                SIPConstants.SIP_DEFAULT_FROMURI,
                callUri.CanonicalAddress,
                null, null, null,
                SIPCallDirection.Out,
                SDP.SDP_MIME_CONTENTTYPE,
                offerSDP.ToString(),
                null);

            uac.Call(callDescriptor);
            uac.ServerTransaction.TransactionTraceMessage += (tx, msg) => Log.LogInformation($"UAC tx trace message. {msg}");

            // Ctrl-c will gracefully exit the call at any point.
            Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
            {
                e.Cancel = true;
                exitMre.Set();
            };

            // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed.
            exitMre.WaitOne();

            Log.LogInformation("Exiting...");

            waveInEvent?.StopRecording();
            audioOutEvent?.Stop();
            rtpSession.CloseSession(null);

            if (!isCallHungup && uac != null)
            {
                if (uac.IsUACAnswered)
                {
                    Log.LogInformation($"Hanging up call to {uac.CallDescriptor.To}.");
                    uac.Hangup();
                }
                else if (!hasCallFailed)
                {
                    Log.LogInformation($"Cancelling call to {uac.CallDescriptor.To}.");
                    uac.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();
            }
        }
Example #15
0
        static void Main(string[] args)
        {
            Console.WriteLine("SIPSorcery user agent server example.");
            Console.WriteLine("Press h to hangup a call or ctrl-c to exit.");

            EnableConsoleLogger();

            IPAddress listenAddress     = IPAddress.Any;
            IPAddress listenIPv6Address = IPAddress.IPv6Any;

            if (args != null && args.Length > 0)
            {
                if (!IPAddress.TryParse(args[0], out var customListenAddress))
                {
                    Log.LogWarning($"Command line argument could not be parsed as an IP address \"{args[0]}\"");
                    listenAddress = IPAddress.Any;
                }
                else
                {
                    if (customListenAddress.AddressFamily == AddressFamily.InterNetwork)
                    {
                        listenAddress = customListenAddress;
                    }
                    if (customListenAddress.AddressFamily == AddressFamily.InterNetworkV6)
                    {
                        listenIPv6Address = customListenAddress;
                    }
                }
            }

            // Set up a default SIP transport.
            var sipTransport = new SIPTransport();

            var localhostCertificate = new X509Certificate2("localhost.pfx");

            // IPv4 channels.
            sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(listenAddress, SIP_LISTEN_PORT)));
            sipTransport.AddSIPChannel(new SIPTCPChannel(new IPEndPoint(listenAddress, SIP_LISTEN_PORT)));
            sipTransport.AddSIPChannel(new SIPTLSChannel(localhostCertificate, new IPEndPoint(listenAddress, SIPS_LISTEN_PORT)));
            //sipTransport.AddSIPChannel(new SIPWebSocketChannel(IPAddress.Any, SIP_WEBSOCKET_LISTEN_PORT));
            //sipTransport.AddSIPChannel(new SIPWebSocketChannel(IPAddress.Any, SIP_SECURE_WEBSOCKET_LISTEN_PORT, localhostCertificate));

            // IPv6 channels.
            sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(listenIPv6Address, SIP_LISTEN_PORT)));
            sipTransport.AddSIPChannel(new SIPTCPChannel(new IPEndPoint(listenIPv6Address, SIP_LISTEN_PORT)));
            sipTransport.AddSIPChannel(new SIPTLSChannel(localhostCertificate, new IPEndPoint(listenIPv6Address, SIPS_LISTEN_PORT)));
            //sipTransport.AddSIPChannel(new SIPWebSocketChannel(IPAddress.IPv6Any, SIP_WEBSOCKET_LISTEN_PORT));
            //sipTransport.AddSIPChannel(new SIPWebSocketChannel(IPAddress.IPv6Any, SIP_SECURE_WEBSOCKET_LISTEN_PORT, localhostCertificate));

            EnableTraceLogs(sipTransport);

            string executableDir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

            // To keep things a bit simpler this example only supports a single call at a time and the SIP server user agent
            // acts as a singleton
            SIPServerUserAgent      uas        = null;
            CancellationTokenSource rtpCts     = null; // Cancellation token to stop the RTP stream.
            RtpAVSession            rtpSession = null;

            // Because this is a server user agent the SIP transport must start listening for client user agents.
            sipTransport.SIPTransportRequestReceived += async(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) =>
            {
                try
                {
                    if (sipRequest.Method == SIPMethodsEnum.INVITE)
                    {
                        SIPSorcery.Sys.Log.Logger.LogInformation($"Incoming call request: {localSIPEndPoint}<-{remoteEndPoint} {sipRequest.URI}.");

                        // Check there's a codec we support in the INVITE offer.
                        var        offerSdp       = SDP.ParseSDPDescription(sipRequest.Body);
                        IPEndPoint dstRtpEndPoint = SDP.GetSDPRTPEndPoint(sipRequest.Body);

                        if (offerSdp.Media.Any(x => x.Media == SDPMediaTypesEnum.audio && x.HasMediaFormat((int)SDPMediaFormatsEnum.PCMU)))
                        {
                            Log.LogDebug($"Client offer contained PCMU audio codec.");
                            rtpSession = new RtpAVSession(
                                new AudioOptions {
                                AudioSource = AudioSourcesEnum.Music, SourceFile = executableDir + "/" + AUDIO_FILE_PCMU
                            }, null);
                            rtpSession.setRemoteDescription(new RTCSessionDescription {
                                type = RTCSdpType.offer, sdp = offerSdp
                            });
                        }

                        if (rtpSession == null)
                        {
                            // Didn't get a match on the codecs we support.
                            SIPResponse noMatchingCodecResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.NotAcceptableHere, null);
                            await sipTransport.SendResponseAsync(noMatchingCodecResponse);
                        }
                        else
                        {
                            // If there's already a call in progress hang it up. Of course this is not ideal for a real softphone or server but it
                            // means this example can be kept simpler.
                            if (uas?.IsHungup == false)
                            {
                                uas?.Hangup(false);
                            }
                            rtpCts?.Cancel();
                            rtpCts = new CancellationTokenSource();

                            UASInviteTransaction uasTransaction = new UASInviteTransaction(sipTransport, sipRequest, null);
                            uas = new SIPServerUserAgent(sipTransport, null, null, null, SIPCallDirection.In, null, null, null, uasTransaction);
                            uas.CallCancelled += (uasAgent) =>
                            {
                                rtpCts?.Cancel();
                                rtpSession.CloseSession(null);
                            };
                            rtpSession.OnRtpClosed += (reason) => uas?.Hangup(false);
                            uas.Progress(SIPResponseStatusCodesEnum.Trying, null, null, null, null);
                            uas.Progress(SIPResponseStatusCodesEnum.Ringing, null, null, null, null);

                            var answerSdp = await rtpSession.createAnswer(null);

                            uas.Answer(SDP.SDP_MIME_CONTENTTYPE, answerSdp.ToString(), null, SIPDialogueTransferModesEnum.NotAllowed);

                            await rtpSession.Start();
                        }
                    }
                    else if (sipRequest.Method == SIPMethodsEnum.BYE)
                    {
                        SIPSorcery.Sys.Log.Logger.LogInformation("Call hungup.");
                        SIPResponse byeResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                        await sipTransport.SendResponseAsync(byeResponse);

                        uas?.Hangup(true);
                        rtpSession?.CloseSession(null);
                        rtpCts?.Cancel();
                    }
                    else if (sipRequest.Method == SIPMethodsEnum.SUBSCRIBE)
                    {
                        SIPResponse notAllowededResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null);
                        await sipTransport.SendResponseAsync(notAllowededResponse);
                    }
                    else if (sipRequest.Method == SIPMethodsEnum.OPTIONS || sipRequest.Method == SIPMethodsEnum.REGISTER)
                    {
                        SIPResponse optionsResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                        await sipTransport.SendResponseAsync(optionsResponse);
                    }
                }
                catch (Exception reqExcp)
                {
                    SIPSorcery.Sys.Log.Logger.LogWarning($"Exception handling {sipRequest.Method}. {reqExcp.Message}");
                }
            };

            ManualResetEvent exitMre = new ManualResetEvent(false);

            Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
            {
                e.Cancel = true;

                SIPSorcery.Sys.Log.Logger.LogInformation("Exiting...");

                Hangup(uas).Wait();

                rtpSession?.CloseSession(null);
                rtpCts?.Cancel();

                if (sipTransport != null)
                {
                    SIPSorcery.Sys.Log.Logger.LogInformation("Shutting down SIP transport...");
                    sipTransport.Shutdown();
                }

                exitMre.Set();
            };

            // Task to handle user key presses.
            Task.Run(() =>
            {
                try
                {
                    while (!exitMre.WaitOne(0))
                    {
                        var keyProps = Console.ReadKey();
                        if (keyProps.KeyChar == 'h' || keyProps.KeyChar == 'q')
                        {
                            Console.WriteLine();
                            Console.WriteLine("Hangup requested by user...");

                            Hangup(uas).Wait();

                            rtpSession?.CloseSession(null);
                            rtpCts?.Cancel();
                        }

                        if (keyProps.KeyChar == 'q')
                        {
                            SIPSorcery.Sys.Log.Logger.LogInformation("Quitting...");

                            if (sipTransport != null)
                            {
                                SIPSorcery.Sys.Log.Logger.LogInformation("Shutting down SIP transport...");
                                sipTransport.Shutdown();
                            }

                            exitMre.Set();
                        }
                    }
                }
                catch (Exception excp)
                {
                    SIPSorcery.Sys.Log.Logger.LogError($"Exception Key Press listener. {excp.Message}.");
                }
            });

            exitMre.WaitOne();
        }
Example #16
0
 public void Call(string destination)
 {
     CreateMediaSockets(SDP.GetSDPRTPEndPoint(m_uas.CallRequest.Body));
     m_xmppCall.PlaceCall(destination + "@" + GOOGLE_VOICE_HOST, GetSDPForXMPPRequest());
 }
Example #17
0
        static void Main()
        {
            Console.WriteLine("SIPSorcery client user agent example.");
            Console.WriteLine("Press ctrl-c to exit.");

            // Plumbing code to facilitate a graceful exit.
            CancellationTokenSource rtpCts = new CancellationTokenSource(); // Cancellation token to stop the RTP stream.
            bool isCallHungup  = false;
            bool hasCallFailed = false;

            AddConsoleLogger();

            SIPURI callUri = SIPURI.ParseSIPURI(DEFAULT_DESTINATION_SIP_URI);

            Log.LogInformation($"Call destination {callUri}.");

            // Set up a default SIP transport.
            var sipTransport = new SIPTransport();

            sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, 0)));

            // Un/comment this line to see/hide each SIP message sent and received.
            EnableTraceLogs(sipTransport);

            // Note this relies on the callURI host being an IP address. If it's a hostname a DNS lookup is required.
            IPAddress localIPAddress = NetServices.GetLocalAddressForRemote(callUri.ToSIPEndPoint().Address);

            // Initialise an RTP session to receive the RTP packets from the remote SIP server.
            var rtpSession = new RTPSession((int)SDPMediaFormatsEnum.PCMU, null, null, true, localIPAddress.AddressFamily);
            var offerSDP   = rtpSession.GetSDP(localIPAddress);

            // Create a client user agent to place a call to a remote SIP server along with event handlers for the different stages of the call.
            var uac = new SIPClientUserAgent(sipTransport);

            uac.CallTrying += (uac, resp) =>
            {
                Log.LogInformation($"{uac.CallDescriptor.To} Trying: {resp.StatusCode} {resp.ReasonPhrase}.");
            };
            uac.CallRinging += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Ringing: {resp.StatusCode} {resp.ReasonPhrase}.");
            uac.CallFailed  += (uac, err) =>
            {
                Log.LogWarning($"{uac.CallDescriptor.To} Failed: {err}");
                hasCallFailed = true;
            };
            uac.CallAnswered += (uac, resp) =>
            {
                if (resp.Status == SIPResponseStatusCodesEnum.Ok)
                {
                    Log.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");
                    rtpSession.DestinationEndPoint = SDP.GetSDPRTPEndPoint(resp.Body);
                    Log.LogDebug($"Remote RTP socket {rtpSession.DestinationEndPoint}.");
                }
                else
                {
                    Log.LogWarning($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");
                }
            };

            // The only incoming request that needs to be explicitly handled for this example is if the remote end hangs up the call.
            sipTransport.SIPTransportRequestReceived += async(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) =>
            {
                if (sipRequest.Method == SIPMethodsEnum.BYE)
                {
                    SIPResponse okResponse = SIPResponse.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                    await sipTransport.SendResponseAsync(okResponse);

                    if (uac.IsUACAnswered)
                    {
                        Log.LogInformation("Call was hungup by remote server.");
                        isCallHungup = true;
                        rtpCts.Cancel();
                    }
                }
            };

            // Wire up the RTP receive session to the default speaker.
            var(audioOutEvent, audioOutProvider) = GetAudioOutputDevice();
            rtpSession.OnReceivedSampleReady    += (sample) =>
            {
                for (int index = 0; index < sample.Length; index++)
                {
                    short  pcm       = NAudio.Codecs.MuLawDecoder.MuLawToLinearSample(sample[index]);
                    byte[] pcmSample = new byte[] { (byte)(pcm & 0xFF), (byte)(pcm >> 8) };
                    audioOutProvider.AddSamples(pcmSample, 0, 2);
                }
            };

            // Send audio packets (in this case silence) to the callee.
            Task.Run(() => SendSilence(rtpSession, rtpCts));

            // Start the thread that places the call.
            SIPCallDescriptor callDescriptor = new SIPCallDescriptor(
                SIPConstants.SIP_DEFAULT_USERNAME,
                null,
                callUri.ToString(),
                SIPConstants.SIP_DEFAULT_FROMURI,
                null, null, null, null,
                SIPCallDirection.Out,
                SDP.SDP_MIME_CONTENTTYPE,
                offerSDP.ToString(),
                null);

            uac.Call(callDescriptor);

            // Ctrl-c will gracefully exit the call at any point.
            Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
            {
                e.Cancel = true;
                rtpCts.Cancel();
            };

            // Give the call some time to answer.
            Task.Delay(3000).Wait();

            // Send some DTMF key presses via RTP events.
            var dtmf5 = new RTPEvent(0x05, false, RTPEvent.DEFAULT_VOLUME, 1200, RTPSession.DTMF_EVENT_PAYLOAD_ID);

            rtpSession.SendDtmfEvent(dtmf5, rtpCts.Token).Wait();
            Task.Delay(2000, rtpCts.Token).Wait();

            var dtmf9 = new RTPEvent(0x09, false, RTPEvent.DEFAULT_VOLUME, 1200, RTPSession.DTMF_EVENT_PAYLOAD_ID);

            rtpSession.SendDtmfEvent(dtmf9, rtpCts.Token).Wait();
            Task.Delay(2000, rtpCts.Token).Wait();

            var dtmf2 = new RTPEvent(0x02, false, RTPEvent.DEFAULT_VOLUME, 1200, RTPSession.DTMF_EVENT_PAYLOAD_ID);

            rtpSession.SendDtmfEvent(dtmf2, rtpCts.Token).Wait();
            Task.Delay(2000, rtpCts.Token).ContinueWith((task) => { }).Wait(); // Don't care about the exception if the cancellation token is set.

            Log.LogInformation("Exiting...");

            rtpCts.Cancel();
            audioOutEvent?.Stop();
            rtpSession.CloseSession(null);

            if (!isCallHungup && uac != null)
            {
                if (uac.IsUACAnswered)
                {
                    Log.LogInformation($"Hanging up call to {uac.CallDescriptor.To}.");
                    uac.Hangup();
                }
                else if (!hasCallFailed)
                {
                    Log.LogInformation($"Cancelling call to {uac.CallDescriptor.To}.");
                    uac.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();
            }
        }
Example #18
0
        static void Main(string[] args)
        {
            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();

            // Check whether an override desination has been entered on the command line.
            SIPURI callUri = SIPURI.ParseSIPURI(DEFAULT_DESTINATION_SIP_URI);

            if (args != null && args.Length > 0)
            {
                if (!SIPURI.TryParse(args[0]))
                {
                    Log.LogWarning($"Command line argument could not be parsed as a SIP URI {args[0]}");
                }
                else
                {
                    callUri = SIPURI.ParseSIPURIRelaxed(args[0]);
                }
            }
            Log.LogInformation($"Call destination {callUri}.");

            // Set up a default SIP transport.
            _sipTransport = new SIPTransport();
            _sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, 0)));

            EnableTraceLogs(_sipTransport);

            var lookupResult = SIPDNSManager.ResolveSIPService(callUri, false);

            Log.LogDebug($"DNS lookup result for {callUri}: {lookupResult?.GetSIPEndPoint()}.");
            var dstAddress = lookupResult.GetSIPEndPoint().Address;

            IPAddress localIPAddress = NetServices.GetLocalAddressForRemote(dstAddress);

            // Initialise an RTP session to receive the RTP packets from the remote SIP server.
            _ourRtpSocket = null;
            Socket controlSocket = null;

            NetServices.CreateRtpSocket(localIPAddress, 48000, 48100, false, out _ourRtpSocket, out controlSocket);
            var rtpRecvSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);
            var rtpSendSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);

            _ourSDP = GetSDP(_ourRtpSocket.LocalEndPoint as IPEndPoint, RTP_ATTRIBUTE_SENDRECV);

            // 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}.");

                    // Only set the remote RTP end point if there hasn't already been a packet received on it.
                    if (_remoteRtpEndPoint == null)
                    {
                        _remoteRtpEndPoint = SDP.GetSDPRTPEndPoint(resp.Body);
                        Log.LogDebug($"Remote RTP socket {_remoteRtpEndPoint}.");
                    }
                }
                else
                {
                    Log.LogWarning($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");
                }
            };
            userAgent.CallHungup += () =>
            {
                Log.LogInformation($"Call hungup by remote party.");
                exitCts.Cancel();
            };
            userAgent.OnReinviteRequest += ReinviteRequestReceived;

            // The only incoming requests that need to be explicitly in this example program are in-dialog
            // re-INVITE requests that are being used to place the call on/off hold.
            _sipTransport.SIPTransportRequestReceived += (localSIPEndPoint, remoteEndPoint, sipRequest) =>
            {
                try
                {
                    if (sipRequest.Header.From != null &&
                        sipRequest.Header.From.FromTag != null &&
                        sipRequest.Header.To != null &&
                        sipRequest.Header.To.ToTag != null)
                    {
                        userAgent.InDialogRequestReceivedAsync(sipRequest).Wait();
                    }
                    else if (sipRequest.Method == SIPMethodsEnum.OPTIONS)
                    {
                        SIPResponse optionsResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                        _sipTransport.SendResponse(optionsResponse);
                    }
                }
                catch (Exception excp)
                {
                    Log.LogError($"Exception processing request. {excp.Message}");
                }
            };

            // It's a good idea to start the RTP receiving socket before the call request is sent.
            // A SIP server will generally start sending RTP as soon as it has processed the incoming call request and
            // being ready to receive will stop any ICMP error response being generated.
            Task.Run(() => RecvRtp(_ourRtpSocket, rtpRecvSession, exitCts));
            Task.Run(() => SendRtp(_ourRtpSocket, rtpSendSession, exitCts));

            // Start the thread that places the call.
            SIPCallDescriptor callDescriptor = new SIPCallDescriptor(
                SIP_USERNAME,
                SIP_PASSWORD,
                callUri.ToString(),
                $"sip:{SIP_USERNAME}@localhost",
                callUri.CanonicalAddress,
                null, null, null,
                SIPCallDirection.Out,
                SDP.SDP_MIME_CONTENTTYPE,
                _ourSDP.ToString(),
                null);

            userAgent.Call(callDescriptor);

            // At this point the call has been initiated and everything will be handled in an event handler.
            Task.Run(() =>
            {
                try
                {
                    while (!exitCts.Token.WaitHandle.WaitOne(0))
                    {
                        var keyProps = Console.ReadKey();
                        if (keyProps.KeyChar == 'h')
                        {
                            // Place call on/off hold.
                            if (userAgent.IsAnswered)
                            {
                                if (_holdStatus == HoldStatus.None)
                                {
                                    Log.LogInformation("Placing the remote call party on hold.");
                                    _holdStatus = HoldStatus.WePutOnHold;
                                    _ourSDP     = GetSDP(_ourRtpSocket.LocalEndPoint as IPEndPoint, RTP_ATTRIBUTE_SENDONLY);
                                    userAgent.SendReInviteRequest(_ourSDP);
                                }
                                else if (_holdStatus == HoldStatus.WePutOnHold)
                                {
                                    Log.LogInformation("Removing the remote call party from hold.");
                                    _holdStatus = HoldStatus.None;
                                    _ourSDP     = GetSDP(_ourRtpSocket.LocalEndPoint as IPEndPoint, RTP_ATTRIBUTE_SENDRECV);
                                    userAgent.SendReInviteRequest(_ourSDP);
                                }
                                else
                                {
                                    Log.LogInformation("Sorry we're already on hold by the remote call party.");
                                }
                            }
                        }
                        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...");

            _ourRtpSocket?.Close();
            controlSocket?.Close();

            if (!isCallHungup && userAgent != null)
            {
                if (userAgent.IsAnswered)
                {
                    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
        }
Example #19
0
        static void Main()
        {
            Console.WriteLine("SIPSorcery client user agent server example.");
            Console.WriteLine("Press ctrl-c to exit.");

            // Logging configuration. Can be ommitted if internal SIPSorcery debug and warning messages are not required.
            var loggerFactory = new Microsoft.Extensions.Logging.LoggerFactory();
            var loggerConfig  = new LoggerConfiguration()
                                .Enrich.FromLogContext()
                                .MinimumLevel.Is(Serilog.Events.LogEventLevel.Debug)
                                .WriteTo.Console()
                                .CreateLogger();

            loggerFactory.AddSerilog(loggerConfig);
            SIPSorcery.Sys.Log.LoggerFactory = loggerFactory;

            // Set up a default SIP transport.
            var sipTransport = new SIPTransport();

            sipTransport.ContactHost = Dns.GetHostName();

            sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT)));
            //sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.IPv6Any, SIP_LISTEN_PORT)));
            //sipTransport.AddSIPChannel(new SIPTCPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT)));
            //sipTransport.AddSIPChannel(new SIPTCPChannel(new IPEndPoint(IPAddress.IPv6Any, SIP_LISTEN_PORT)));

            //if (File.Exists("localhost.pfx"))
            //{
            //    var certificate = new X509Certificate2(@"localhost.pfx", "");
            //    sipTransport.AddSIPChannel(new SIPTLSChannel(certificate, new IPEndPoint(IPAddress.Any, SIPS_LISTEN_PORT)));
            //    sipTransport.AddSIPChannel(new SIPTLSChannel(certificate, new IPEndPoint(IPAddress.IPv6Any, SIPS_LISTEN_PORT)));
            //}

            //EnableTraceLogs(sipTransport);

            // To keep things a bit simpler this example only supports a single call at a time and the SIP server user agent
            // acts as a singleton
            SIPServerUserAgent      uas    = null;
            CancellationTokenSource rtpCts = null; // Cancellation token to stop the RTP stream.

            // Because this is a server user agent the SIP transport must start listening for client user agents.
            sipTransport.SIPTransportRequestReceived += async(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) =>
            {
                if (sipRequest.Method == SIPMethodsEnum.INVITE)
                {
                    SIPSorcery.Sys.Log.Logger.LogInformation("Incoming call request: " + localSIPEndPoint + "<-" + remoteEndPoint + " " + sipRequest.URI.ToString() + ".");
                    //SIPSorcery.Sys.Log.Logger.LogDebug(sipRequest.ToString());

                    // If there's already a call in progress hang it up. Of course this is not ideal for a real softphone or server but it
                    // means this example can be kept simpler.
                    if (uas?.IsHungup == false)
                    {
                        uas?.Hangup(false);
                    }
                    rtpCts?.Cancel();

                    UASInviteTransaction uasTransaction = sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, null);
                    uas    = new SIPServerUserAgent(sipTransport, null, null, null, SIPCallDirection.In, null, null, null, uasTransaction);
                    rtpCts = new CancellationTokenSource();

                    // In practice there could be a number of reasons to reject the call. Unsupported extensions, mo matching codecs etc. etc.
                    if (sipRequest.Header.HasUnknownRequireExtension)
                    {
                        // The caller requires an extension that we don't support.
                        SIPSorcery.Sys.Log.Logger.LogWarning($"Rejecting incoming call due to one or more required exensions not being supported, required extensions: {sipRequest.Header.Require}.");
                        uas.Reject(SIPResponseStatusCodesEnum.NotImplemented, "Unsupported Require Extension", null);
                    }
                    else
                    {
                        uas.Progress(SIPResponseStatusCodesEnum.Trying, null, null, null, null);
                        uas.Progress(SIPResponseStatusCodesEnum.Ringing, null, null, null, null);

                        // Simulating answer delay to test provisional response retransmits.
                        await Task.Delay(2000);

                        // Initialise an RTP session to receive the RTP packets from the remote SIP server.
                        Socket rtpSocket     = null;
                        Socket controlSocket = null;
                        NetServices.CreateRtpSocket(localSIPEndPoint.Address, 49000, 49100, false, out rtpSocket, out controlSocket);

                        IPEndPoint rtpEndPoint    = rtpSocket.LocalEndPoint as IPEndPoint;
                        IPEndPoint dstRtpEndPoint = SDP.GetSDPRTPEndPoint(sipRequest.Body);
                        var        rtpSession     = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);

                        var rtpTask = Task.Run(() => SendRecvRtp(rtpSocket, rtpSession, dstRtpEndPoint, AUDIO_FILE, rtpCts))
                                      .ContinueWith(_ => { if (uas?.IsHungup == false)
                                                           {
                                                               uas?.Hangup(false);
                                                           }
                                                    });

                        uas.Answer(SDP.SDP_MIME_CONTENTTYPE, GetSDP(rtpEndPoint).ToString(), null, SIPDialogueTransferModesEnum.NotAllowed);
                    }
                }
                else if (sipRequest.Method == SIPMethodsEnum.BYE)
                {
                    SIPSorcery.Sys.Log.Logger.LogInformation("Call hungup.");
                    SIPNonInviteTransaction byeTransaction = sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, null);
                    SIPResponse             byeResponse    = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                    byeTransaction.SendFinalResponse(byeResponse);
                    uas?.Hangup(true);
                    rtpCts?.Cancel();
                }
                else if (sipRequest.Method == SIPMethodsEnum.OPTIONS)
                {
                    try
                    {
                        SIPSorcery.Sys.Log.Logger.LogInformation($"{localSIPEndPoint.ToString()}<-{remoteEndPoint.ToString()}: {sipRequest.StatusLine}");
                        //SIPSorcery.Sys.Log.Logger.LogDebug(sipRequest.ToString());
                        SIPResponse optionsResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                        sipTransport.SendResponse(optionsResponse);
                    }
                    catch (Exception optionsExcp)
                    {
                        SIPSorcery.Sys.Log.Logger.LogWarning($"Failed to send SIP OPTIONS response. {optionsExcp.Message}");
                    }
                }
            };

            Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
            {
                e.Cancel = true;

                SIPSorcery.Sys.Log.Logger.LogInformation("Exiting...");
                rtpCts?.Cancel();
                if (uas?.IsHungup == false)
                {
                    uas?.Hangup(false);

                    // Give the BYE or CANCEL request time to be transmitted.
                    SIPSorcery.Sys.Log.Logger.LogInformation("Waiting 1s for call to hangup...");
                    Task.Delay(1000).Wait();
                }

                if (sipTransport != null)
                {
                    SIPSorcery.Sys.Log.Logger.LogInformation("Shutting down SIP transport...");
                    sipTransport.Shutdown();
                }
            };
        }
Example #20
0
        static void Main(string[] args)
        {
            Console.WriteLine("SIPSorcery user agent server example.");
            Console.WriteLine("Press h to hangup a call or ctrl-c to exit.");

            EnableConsoleLogger();

            IPAddress listenAddress     = IPAddress.Any;
            IPAddress listenIPv6Address = IPAddress.IPv6Any;

            if (args != null && args.Length > 0)
            {
                if (!IPAddress.TryParse(args[0], out var customListenAddress))
                {
                    Log.LogWarning($"Command line argument could not be parsed as an IP address \"{args[0]}\"");
                    listenAddress = IPAddress.Any;
                }
                else
                {
                    if (customListenAddress.AddressFamily == AddressFamily.InterNetwork)
                    {
                        listenAddress = customListenAddress;
                    }
                    if (customListenAddress.AddressFamily == AddressFamily.InterNetworkV6)
                    {
                        listenIPv6Address = customListenAddress;
                    }
                }
            }

            // Set up a default SIP transport.
            var sipTransport = new SIPTransport();

            var localhostCertificate = new X509Certificate2("localhost.pfx");

            // IPv4 channels.
            sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(listenAddress, SIP_LISTEN_PORT)));
            sipTransport.AddSIPChannel(new SIPTCPChannel(new IPEndPoint(listenAddress, SIP_LISTEN_PORT)));
            sipTransport.AddSIPChannel(new SIPTLSChannel(localhostCertificate, new IPEndPoint(listenAddress, SIPS_LISTEN_PORT)));
            sipTransport.AddSIPChannel(new SIPWebSocketChannel(IPAddress.Any, SIP_WEBSOCKET_LISTEN_PORT));
            sipTransport.AddSIPChannel(new SIPWebSocketChannel(IPAddress.Any, SIP_SECURE_WEBSOCKET_LISTEN_PORT, localhostCertificate));

            // IPv6 channels.
            sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(listenIPv6Address, SIP_LISTEN_PORT)));
            sipTransport.AddSIPChannel(new SIPTCPChannel(new IPEndPoint(listenIPv6Address, SIP_LISTEN_PORT)));
            sipTransport.AddSIPChannel(new SIPTLSChannel(localhostCertificate, new IPEndPoint(listenIPv6Address, SIPS_LISTEN_PORT)));
            sipTransport.AddSIPChannel(new SIPWebSocketChannel(IPAddress.IPv6Any, SIP_WEBSOCKET_LISTEN_PORT));
            sipTransport.AddSIPChannel(new SIPWebSocketChannel(IPAddress.IPv6Any, SIP_SECURE_WEBSOCKET_LISTEN_PORT, localhostCertificate));

            EnableTraceLogs(sipTransport);

            // To keep things a bit simpler this example only supports a single call at a time and the SIP server user agent
            // acts as a singleton
            SIPServerUserAgent      uas    = null;
            CancellationTokenSource rtpCts = null; // Cancellation token to stop the RTP stream.
            Socket rtpSocket     = null;
            Socket controlSocket = null;

            // Because this is a server user agent the SIP transport must start listening for client user agents.
            sipTransport.SIPTransportRequestReceived += (SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) =>
            {
                try
                {
                    if (sipRequest.Method == SIPMethodsEnum.INVITE)
                    {
                        SIPSorcery.Sys.Log.Logger.LogInformation($"Incoming call request: {localSIPEndPoint}<-{remoteEndPoint} {sipRequest.URI}.");

                        // Check there's a codec we support in the INVITE offer.
                        var        offerSdp       = SDP.ParseSDPDescription(sipRequest.Body);
                        IPEndPoint dstRtpEndPoint = SDP.GetSDPRTPEndPoint(sipRequest.Body);
                        RTPSession rtpSession     = null;
                        string     audioFile      = null;

                        if (offerSdp.Media.Any(x => x.Media == SDPMediaTypesEnum.audio && x.HasMediaFormat((int)RTPPayloadTypesEnum.G722)))
                        {
                            Log.LogDebug($"Using G722 RTP media type and audio file {AUDIO_FILE_G722}.");
                            rtpSession = new RTPSession((int)RTPPayloadTypesEnum.G722, null, null);
                            audioFile  = AUDIO_FILE_G722;
                        }
                        else if (offerSdp.Media.Any(x => x.Media == SDPMediaTypesEnum.audio && x.HasMediaFormat((int)RTPPayloadTypesEnum.PCMU)))
                        {
                            Log.LogDebug($"Using PCMU RTP media type and audio file {AUDIO_FILE_PCMU}.");
                            rtpSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);
                            audioFile  = AUDIO_FILE_PCMU;
                        }

                        if (rtpSession == null)
                        {
                            // Didn't get a match on the codecs we support.
                            SIPResponse noMatchingCodecResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.NotAcceptableHere, null);
                            sipTransport.SendResponse(noMatchingCodecResponse);
                        }
                        else
                        {
                            // If there's already a call in progress hang it up. Of course this is not ideal for a real softphone or server but it
                            // means this example can be kept simpler.
                            if (uas?.IsHungup == false)
                            {
                                uas?.Hangup(false);
                            }
                            rtpCts?.Cancel();

                            UASInviteTransaction uasTransaction = sipTransport.CreateUASTransaction(sipRequest, null);
                            uas    = new SIPServerUserAgent(sipTransport, null, null, null, SIPCallDirection.In, null, null, null, uasTransaction);
                            rtpCts = new CancellationTokenSource();

                            uas.Progress(SIPResponseStatusCodesEnum.Trying, null, null, null, null);
                            uas.Progress(SIPResponseStatusCodesEnum.Ringing, null, null, null, null);

                            // Initialise an RTP session to receive the RTP packets from the remote SIP server.
                            NetServices.CreateRtpSocket(dstRtpEndPoint.AddressFamily == AddressFamily.InterNetworkV6 ? IPAddress.IPv6Any : IPAddress.Any, RTP_PORT_START, RTP_PORT_END, false, out rtpSocket, out controlSocket);

                            // The RTP socket is listening on IPAddress.Any but the IP address placed into the SDP needs to be one the caller can reach.
                            IPAddress  rtpAddress  = NetServices.GetLocalAddressForRemote(dstRtpEndPoint.Address);
                            IPEndPoint rtpEndPoint = new IPEndPoint(rtpAddress, (rtpSocket.LocalEndPoint as IPEndPoint).Port);

                            var rtpTask = Task.Run(() => SendRecvRtp(rtpSocket, rtpSession, dstRtpEndPoint, audioFile, rtpCts))
                                          .ContinueWith(_ =>
                            {
                                if (uas?.IsHungup == false)
                                {
                                    uas?.Hangup(false);
                                }
                            });

                            uas.Answer(SDP.SDP_MIME_CONTENTTYPE, GetSDP(rtpEndPoint).ToString(), null, SIPDialogueTransferModesEnum.NotAllowed);
                        }
                    }
                    else if (sipRequest.Method == SIPMethodsEnum.BYE)
                    {
                        SIPSorcery.Sys.Log.Logger.LogInformation("Call hungup.");
                        SIPNonInviteTransaction byeTransaction = sipTransport.CreateNonInviteTransaction(sipRequest, null);
                        SIPResponse             byeResponse    = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                        byeTransaction.SendFinalResponse(byeResponse);
                        uas?.Hangup(true);
                        rtpCts?.Cancel();
                        rtpSocket?.Close();
                        controlSocket?.Close();
                    }
                    else if (sipRequest.Method == SIPMethodsEnum.SUBSCRIBE)
                    {
                        SIPResponse notAllowededResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null);
                        sipTransport.SendResponse(notAllowededResponse);
                    }
                    else if (sipRequest.Method == SIPMethodsEnum.OPTIONS || sipRequest.Method == SIPMethodsEnum.REGISTER)
                    {
                        SIPResponse optionsResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                        sipTransport.SendResponse(optionsResponse);
                    }
                }
                catch (Exception reqExcp)
                {
                    SIPSorcery.Sys.Log.Logger.LogWarning($"Exception handling {sipRequest.Method}. {reqExcp.Message}");
                }
            };

            ManualResetEvent exitMre = new ManualResetEvent(false);

            Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
            {
                e.Cancel = true;

                SIPSorcery.Sys.Log.Logger.LogInformation("Exiting...");

                Hangup(uas).Wait();

                rtpCts?.Cancel();
                rtpSocket?.Close();
                controlSocket?.Close();

                if (sipTransport != null)
                {
                    SIPSorcery.Sys.Log.Logger.LogInformation("Shutting down SIP transport...");
                    sipTransport.Shutdown();
                }

                exitMre.Set();
            };

            Task.Run(() =>
            {
                try
                {
                    while (!exitMre.WaitOne(0))
                    {
                        var keyProps = Console.ReadKey();
                        if (keyProps.KeyChar == 'h' || keyProps.KeyChar == 'q')
                        {
                            Console.WriteLine();
                            Console.WriteLine("Hangup requested by user...");

                            Hangup(uas).Wait();

                            rtpCts?.Cancel();
                            rtpSocket?.Close();
                            controlSocket?.Close();
                        }

                        if (keyProps.KeyChar == 'q')
                        {
                            SIPSorcery.Sys.Log.Logger.LogInformation("Quitting...");

                            if (sipTransport != null)
                            {
                                SIPSorcery.Sys.Log.Logger.LogInformation("Shutting down SIP transport...");
                                sipTransport.Shutdown();
                            }

                            exitMre.Set();
                        }
                    }
                }
                catch (Exception excp)
                {
                    SIPSorcery.Sys.Log.Logger.LogError($"Exception Key Press listener. {excp.Message}.");
                }
            });

            exitMre.WaitOne();
        }
Example #21
0
        static void Main()
        {
            Console.WriteLine("SIPSorcery client user agent example.");
            Console.WriteLine("Press ctrl-c to exit.");

            // Plumbing code to facilitate a graceful exit.
            CancellationTokenSource rtpCts = new CancellationTokenSource(); // Cancellation token to stop the RTP stream.
            bool isCallHungup  = false;
            bool hasCallFailed = false;

            AddConsoleLogger();

            SIPURI callUri = SIPURI.ParseSIPURI(DEFAULT_DESTINATION_SIP_URI);

            Log.LogInformation($"Call destination {callUri}.");

            // Set up a default SIP transport.
            var sipTransport = new SIPTransport();
            int port         = SIPConstants.DEFAULT_SIP_PORT + 1000;

            sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, port)));

            // Uncomment this line to see each SIP message sent and received.
            EnableTraceLogs(sipTransport);

            // Send an OPTIONS request to determine the local IP address to use for the RTP socket.
            var optionsTask = SendOptionsTaskAsync(sipTransport, callUri);
            var result      = Task.WhenAny(optionsTask, Task.Delay(SIP_REQUEST_TIMEOUT_MILLISECONDS));

            result.Wait();

            if (optionsTask.IsCompletedSuccessfully == false || optionsTask.Result == null)
            {
                Log.LogError($"OPTIONS request to {callUri} failed.");
            }
            else
            {
                IPAddress localIPAddress = optionsTask.Result;

                // Initialise an RTP session to receive the RTP packets from the remote SIP server.
                Socket rtpSocket     = null;
                Socket controlSocket = null;
                NetServices.CreateRtpSocket(localIPAddress, 49000, 49100, false, out rtpSocket, out controlSocket);
                var rtpRecvSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);
                var rtpSendSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);

                // Create a client user agent to place a call to a remote SIP server along with event handlers for the different stages of the call.
                var uac = new SIPClientUserAgent(sipTransport);

                uac.CallTrying += (uac, resp) =>
                {
                    Log.LogInformation($"{uac.CallDescriptor.To} Trying: {resp.StatusCode} {resp.ReasonPhrase}.");
                    Log.LogDebug(resp.ToString());
                };
                uac.CallRinging += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Ringing: {resp.StatusCode} {resp.ReasonPhrase}.");
                uac.CallFailed  += (uac, err) =>
                {
                    Log.LogWarning($"{uac.CallDescriptor.To} Failed: {err}");
                    hasCallFailed = true;
                };
                uac.CallAnswered += (uac, resp) =>
                {
                    if (resp.Status == SIPResponseStatusCodesEnum.Ok)
                    {
                        Log.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");

                        _remoteRtpEndPoint = SDP.GetSDPRTPEndPoint(resp.Body);

                        Log.LogDebug($"Remote RTP socket {_remoteRtpEndPoint}.");
                    }
                    else
                    {
                        Log.LogWarning($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");
                    }
                };

                // The only incoming request that needs to be explicitly handled for this example is if the remote end hangs up the call.
                sipTransport.SIPTransportRequestReceived += (SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) =>
                {
                    if (sipRequest.Method == SIPMethodsEnum.BYE)
                    {
                        SIPNonInviteTransaction byeTransaction = sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, null);
                        SIPResponse             byeResponse    = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                        byeTransaction.SendFinalResponse(byeResponse);

                        if (uac.IsUACAnswered)
                        {
                            Log.LogInformation("Call was hungup by remote server.");
                            isCallHungup = true;
                            rtpCts.Cancel();
                        }
                    }
                };

                // It's a good idea to start the RTP receiving socket before the call request is sent.
                // A SIP server will generally start sending RTP as soon as it has processed the incoming call request and
                // being ready to receive will stop any ICMP error response being generated.
                Task.Run(() => RecvRtp(rtpSocket, rtpRecvSession, rtpCts));
                Task.Run(() => SendRtp(rtpSocket, rtpSendSession, rtpCts));

                // Start the thread that places the call.
                SIPCallDescriptor callDescriptor = new SIPCallDescriptor(
                    SIPConstants.SIP_DEFAULT_USERNAME,
                    null,
                    callUri.ToString(),
                    SIPConstants.SIP_DEFAULT_FROMURI,
                    null, null, null, null,
                    SIPCallDirection.Out,
                    SDP.SDP_MIME_CONTENTTYPE,
                    GetSDP(rtpSocket.LocalEndPoint as IPEndPoint).ToString(),
                    null);

                uac.Call(callDescriptor);

                // Ctrl-c will gracefully exit the call at any point.
                Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
                {
                    e.Cancel = true;
                    rtpCts.Cancel();
                };

                // At this point the call is established. We'll wait for a few seconds and then transfer.
                Task.Delay(DELAY_UNTIL_TRANSFER_MILLISECONDS).Wait();

                SIPRequest referRequest         = GetReferRequest(uac, SIPURI.ParseSIPURI(TRANSFER_DESTINATION_SIP_URI));
                SIPNonInviteTransaction referTx = sipTransport.CreateNonInviteTransaction(referRequest, referRequest.RemoteSIPEndPoint, referRequest.LocalSIPEndPoint, null);

                referTx.NonInviteTransactionFinalResponseReceived += (SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPTransaction sipTransaction, SIPResponse sipResponse) =>
                {
                    if (sipResponse.Header.CSeqMethod == SIPMethodsEnum.REFER && sipResponse.Status == SIPResponseStatusCodesEnum.Accepted)
                    {
                        Log.LogInformation("Call transfer was accepted by remote server.");
                        isCallHungup = true;
                        rtpCts.Cancel();
                    }
                };

                referTx.SendReliableRequest();

                // At this point the call transfer has been initiated and everything will be handled in an event handler or on the RTP
                // receive task. The code below is to gracefully exit.

                // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed.
                rtpCts.Token.WaitHandle.WaitOne();

                Log.LogInformation("Exiting...");

                rtpSocket?.Close();
                controlSocket?.Close();

                if (!isCallHungup && uac != null)
                {
                    if (uac.IsUACAnswered)
                    {
                        Log.LogInformation($"Hanging up call to {uac.CallDescriptor.To}.");
                        uac.Hangup();
                    }
                    else if (!hasCallFailed)
                    {
                        Log.LogInformation($"Cancelling call to {uac.CallDescriptor.To}.");
                        uac.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();
            }
        }
Example #22
0
        private static ConcurrentQueue <RTPEvent> _dtmfEvents = new ConcurrentQueue <RTPEvent>(); // Add a DTMF event to this queue to have the it sent

        static void Main()
        {
            Console.WriteLine("SIPSorcery client user agent example.");
            Console.WriteLine("Press ctrl-c to exit.");

            // Plumbing code to facilitate a graceful exit.
            CancellationTokenSource rtpCts = new CancellationTokenSource(); // Cancellation token to stop the RTP stream.
            bool isCallHungup  = false;
            bool hasCallFailed = false;

            AddConsoleLogger();

            SIPURI callUri = SIPURI.ParseSIPURI(DEFAULT_DESTINATION_SIP_URI);

            Log.LogInformation($"Call destination {callUri}.");

            // Set up a default SIP transport.
            var sipTransport = new SIPTransport();

            sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, 0)));

            // Un/comment this line to see/hide each SIP message sent and received.
            EnableTraceLogs(sipTransport);

            // Note this relies on the callURI host being an IP address. If it's a hostname a DNS lookup is required.
            IPAddress localIPAddress = NetServices.GetLocalAddressForRemote(callUri.ToSIPEndPoint().Address);

            // Initialise an RTP session to receive the RTP packets from the remote SIP server.
            Socket rtpSocket     = null;
            Socket controlSocket = null;

            NetServices.CreateRtpSocket(localIPAddress, 49000, 49100, false, out rtpSocket, out controlSocket);
            var rtpRecvSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);
            var rtpSendSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);

            // Create a client user agent to place a call to a remote SIP server along with event handlers for the different stages of the call.
            var uac = new SIPClientUserAgent(sipTransport);

            uac.CallTrying += (uac, resp) =>
            {
                Log.LogInformation($"{uac.CallDescriptor.To} Trying: {resp.StatusCode} {resp.ReasonPhrase}.");
            };
            uac.CallRinging += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Ringing: {resp.StatusCode} {resp.ReasonPhrase}.");
            uac.CallFailed  += (uac, err) =>
            {
                Log.LogWarning($"{uac.CallDescriptor.To} Failed: {err}");
                hasCallFailed = true;
            };
            uac.CallAnswered += (uac, resp) =>
            {
                if (resp.Status == SIPResponseStatusCodesEnum.Ok)
                {
                    Log.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");

                    _remoteRtpEndPoint = SDP.GetSDPRTPEndPoint(resp.Body);

                    Log.LogDebug($"Remote RTP socket {_remoteRtpEndPoint}.");
                }
                else
                {
                    Log.LogWarning($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");
                }
            };

            // The only incoming request that needs to be explicitly handled for this example is if the remote end hangs up the call.
            sipTransport.SIPTransportRequestReceived += (SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) =>
            {
                if (sipRequest.Method == SIPMethodsEnum.BYE)
                {
                    SIPNonInviteTransaction byeTransaction = sipTransport.CreateNonInviteTransaction(sipRequest, null);
                    SIPResponse             byeResponse    = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                    byeTransaction.SendFinalResponse(byeResponse);

                    if (uac.IsUACAnswered)
                    {
                        Log.LogInformation("Call was hungup by remote server.");
                        isCallHungup = true;
                        rtpCts.Cancel();
                    }
                }
            };

            // It's a good idea to start the RTP receiving socket before the call request is sent.
            // A SIP server will generally start sending RTP as soon as it has processed the incoming call request and
            // being ready to receive will stop any ICMP error response being generated.
            Task.Run(() => RecvRtp(rtpSocket, rtpRecvSession, rtpCts));
            Task.Run(() => SendRtp(rtpSocket, rtpSendSession, rtpCts));

            // Start the thread that places the call.
            SIPCallDescriptor callDescriptor = new SIPCallDescriptor(
                SIPConstants.SIP_DEFAULT_USERNAME,
                null,
                callUri.ToString(),
                SIPConstants.SIP_DEFAULT_FROMURI,
                null, null, null, null,
                SIPCallDirection.Out,
                SDP.SDP_MIME_CONTENTTYPE,
                GetSDP(rtpSocket.LocalEndPoint as IPEndPoint, RTPPayloadTypesEnum.PCMU).ToString(),
                null);

            uac.Call(callDescriptor);

            // Ctrl-c will gracefully exit the call at any point.
            Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
            {
                e.Cancel = true;
                rtpCts.Cancel();
            };

            // At this point the call has been initiated and everything will be handled in an event handler or on the RTP
            // receive task. The code below is to gracefully exit.
            Task.Delay(3000).Wait();

            // Add some DTMF events to the queue. These will be transmitted by the SendRtp thread.
            _dtmfEvents.Enqueue(new RTPEvent(0x05, false, RTPEvent.DEFAULT_VOLUME, 1200, DTMF_EVENT_PAYLOAD_ID));
            Task.Delay(2000, rtpCts.Token).Wait();
            _dtmfEvents.Enqueue(new RTPEvent(0x09, false, RTPEvent.DEFAULT_VOLUME, 1200, DTMF_EVENT_PAYLOAD_ID));
            Task.Delay(2000, rtpCts.Token).Wait();
            _dtmfEvents.Enqueue(new RTPEvent(0x02, false, RTPEvent.DEFAULT_VOLUME, 1200, DTMF_EVENT_PAYLOAD_ID));
            Task.Delay(2000, rtpCts.Token).Wait();

            Log.LogInformation("Exiting...");

            rtpCts.Cancel();
            rtpSocket?.Close();
            controlSocket?.Close();

            if (!isCallHungup && uac != null)
            {
                if (uac.IsUACAnswered)
                {
                    Log.LogInformation($"Hanging up call to {uac.CallDescriptor.To}.");
                    uac.Hangup();
                }
                else if (!hasCallFailed)
                {
                    Log.LogInformation($"Cancelling call to {uac.CallDescriptor.To}.");
                    uac.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();
            }
        }
Example #23
0
        private static readonly int RTP_REPORTING_PERIOD_SECONDS = 5;       // Period at which to write RTP stats.

        static void Main()
        {
            Console.WriteLine("SIPSorcery client user agent example.");
            Console.WriteLine("Press ctrl-c to exit.");

            // Plumbing code to facilitate a graceful exit.
            CancellationTokenSource cts = new CancellationTokenSource();
            bool isCallHungup           = false;
            bool hasCallFailed          = false;

            // Logging configuration. Can be ommitted if internal SIPSorcery debug and warning messages are not required.
            var loggerFactory = new Microsoft.Extensions.Logging.LoggerFactory();
            var loggerConfig  = new LoggerConfiguration()
                                .Enrich.FromLogContext()
                                .MinimumLevel.Is(Serilog.Events.LogEventLevel.Debug)
                                .WriteTo.Console()
                                .CreateLogger();

            loggerFactory.AddSerilog(loggerConfig);
            SIPSorcery.Sys.Log.LoggerFactory = loggerFactory;

            // Set up a default SIP transport.
            IPAddress defaultAddr  = LocalIPConfig.GetDefaultIPv4Address();
            var       sipTransport = new SIPTransport(SIPDNSManager.ResolveSIPService, new SIPTransactionEngine());
            int       port         = FreePort.FindNextAvailableUDPPort(SIPConstants.DEFAULT_SIP_PORT + 2);
            var       sipChannel   = new SIPUDPChannel(new IPEndPoint(defaultAddr, port));

            sipTransport.AddSIPChannel(sipChannel);

            // Initialise an RTP session to receive the RTP packets from the remote SIP server.
            Socket rtpSocket     = null;
            Socket controlSocket = null;

            NetServices.CreateRtpSocket(defaultAddr, 49000, 49100, false, out rtpSocket, out controlSocket);
            var rtpSendSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);

            // Create a client user agent to place a call to a remote SIP server along with event handlers for the different stages of the call.
            var uac = new SIPClientUserAgent(sipTransport);

            uac.CallTrying  += (uac, resp) => SIPSorcery.Sys.Log.Logger.LogInformation($"{uac.CallDescriptor.To} Trying: {resp.StatusCode} {resp.ReasonPhrase}.");
            uac.CallRinging += (uac, resp) => SIPSorcery.Sys.Log.Logger.LogInformation($"{uac.CallDescriptor.To} Ringing: {resp.StatusCode} {resp.ReasonPhrase}.");
            uac.CallFailed  += (uac, err) =>
            {
                SIPSorcery.Sys.Log.Logger.LogWarning($"{uac.CallDescriptor.To} Failed: {err}");
                hasCallFailed = true;
            };
            uac.CallAnswered += (uac, resp) =>
            {
                if (resp.Status == SIPResponseStatusCodesEnum.Ok)
                {
                    SIPSorcery.Sys.Log.Logger.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");
                    IPEndPoint remoteRtpEndPoint = SDP.GetSDPRTPEndPoint(resp.Body);

                    SIPSorcery.Sys.Log.Logger.LogDebug($"Sending initial RTP packet to remote RTP socket {remoteRtpEndPoint}.");

                    // Send a dummy packet to open the NAT session on the RTP path.
                    rtpSendSession.SendAudioFrame(rtpSocket, remoteRtpEndPoint, 0, new byte[] { 0x00 });
                }
                else
                {
                    SIPSorcery.Sys.Log.Logger.LogWarning($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");
                }
            };

            // The only incoming request that needs to be explicitly handled for this example is if the remote end hangs up the call.
            sipTransport.SIPTransportRequestReceived += (SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) =>
            {
                if (sipRequest.Method == SIPMethodsEnum.BYE)
                {
                    SIPNonInviteTransaction byeTransaction = sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, null);
                    SIPResponse             byeResponse    = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                    byeTransaction.SendFinalResponse(byeResponse);

                    if (uac.IsUACAnswered)
                    {
                        SIPSorcery.Sys.Log.Logger.LogInformation("Call was hungup by remote server.");
                        isCallHungup = true;
                        cts.Cancel();
                    }
                }
            };

            // It's a good idea to start the RTP receiving socket before the call request is sent.
            // A SIP server will generally start sending RTP as soon as it has processed the incoming call request and
            // being ready to receive will stop any ICMP error response being generated.
            Task.Run(() => SendRecvRtp(rtpSocket, rtpSendSession, cts));

            // Start the thread that places the call.
            SIPCallDescriptor callDescriptor = new SIPCallDescriptor(
                SIPConstants.SIP_DEFAULT_USERNAME,
                null,
                DESTINATION_SIP_URI,
                SIPConstants.SIP_DEFAULT_FROMURI,
                null, null, null, null,
                SIPCallDirection.Out,
                SDP.SDP_MIME_CONTENTTYPE,
                GetSDP(rtpSocket.LocalEndPoint as IPEndPoint).ToString(),
                null);

            uac.Call(callDescriptor);

            // At this point the call has been initiated and everything will be handled in an event handler or on the RTP
            // receive task. The code below is to gracefully exit.
            // Ctrl-c will gracefully exit the call at any point.
            Console.CancelKeyPress += async delegate(object sender, ConsoleCancelEventArgs e)
            {
                e.Cancel = true;
                cts.Cancel();

                SIPSorcery.Sys.Log.Logger.LogInformation("Exiting...");

                rtpSocket?.Close();
                controlSocket?.Close();

                if (!isCallHungup && uac != null)
                {
                    if (uac.IsUACAnswered)
                    {
                        SIPSorcery.Sys.Log.Logger.LogInformation($"Hanging up call to {uac.CallDescriptor.To}.");
                        uac.Hangup();
                    }
                    else if (!hasCallFailed)
                    {
                        SIPSorcery.Sys.Log.Logger.LogInformation($"Cancelling call to {uac.CallDescriptor.To}.");
                        uac.Cancel();
                    }

                    // Give the BYE or CANCEL request time to be transmitted.
                    SIPSorcery.Sys.Log.Logger.LogInformation("Waiting 1s for call to clean up...");
                    await Task.Delay(1000);
                }

                SIPSorcery.Net.DNSManager.Stop();

                if (sipTransport != null)
                {
                    SIPSorcery.Sys.Log.Logger.LogInformation("Shutting down SIP transport...");
                    sipTransport.Shutdown();
                }
            };
        }
Example #24
0
        private static void SIPTransportRequestReceived(SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest)
        {
            if (sipRequest.Method == SIPMethodsEnum.BYE)
            {
                var rtpJob = (from job in m_rtpJobs.Values where job.UAS.CallRequest.Header.CallId == sipRequest.Header.CallId select job).FirstOrDefault();

                if (rtpJob != null)
                {
                    rtpJob.Stop();
                    // Call has been hungup by remote end.
                    Console.WriteLine("Call hungup by client: " + localSIPEndPoint + "<-" + remoteEndPoint + " " + sipRequest.URI.ToString() + ".\n");
                    Publish(rtpJob.QueueName, "BYE request received from " + remoteEndPoint + " for " + sipRequest.URI.ToString() + ".");
                    //Console.WriteLine("Request Received " + localSIPEndPoint + "<-" + remoteEndPoint + "\n" + sipRequest.ToString());
                    //m_uas.SIPDialogue.Hangup(m_sipTransport, null);
                    SIPResponse okResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                    m_sipTransport.SendResponse(okResponse);
                }
                else
                {
                    Console.WriteLine("Unmatched BYE request received for " + sipRequest.URI.ToString() + ".\n");
                    SIPResponse noCallLegResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist, null);
                    m_sipTransport.SendResponse(noCallLegResponse);
                }
            }
            else if (sipRequest.Method == SIPMethodsEnum.INVITE)
            {
                Console.WriteLine("Incoming call request: " + localSIPEndPoint + "<-" + remoteEndPoint + " " + sipRequest.URI.ToString() + ".\n");
                Publish(sipRequest.URI.User, "INVITE request received from " + remoteEndPoint + " for " + sipRequest.URI.ToString() + ".");

                Console.WriteLine(sipRequest.Body);

                SIPPacketMangler.MangleSIPRequest(SIPMonitorServerTypesEnum.Unknown, sipRequest, null, LogTraceMessage);

                UASInviteTransaction uasTransaction = m_sipTransport.CreateUASTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, null);
                var uas = new SIPServerUserAgent(m_sipTransport, null, null, null, SIPCallDirection.In, null, null, LogTraceMessage, uasTransaction);
                uas.CallCancelled += UASCallCancelled;

                RTPDiagnosticsJob rtpJob = new RTPDiagnosticsJob(m_rtpListenIPAddress, m_publicIPAddress, uas, sipRequest);

                string sdpAddress = SDP.GetSDPRTPEndPoint(sipRequest.Body).Address.ToString();

                // Only mangle if there is something to change. For example the server could be on the same private subnet in which case it can't help.
                IPEndPoint expectedRTPEndPoint = new IPEndPoint(rtpJob.RemoteRTPEndPoint.Address, rtpJob.RemoteRTPEndPoint.Port);
                if (IPSocket.IsPrivateAddress(rtpJob.RemoteRTPEndPoint.Address.ToString()))
                {
                    expectedRTPEndPoint.Address = remoteEndPoint.Address;
                }

                Publish(sipRequest.URI.User, "Advertised RTP remote socket " + rtpJob.RemoteRTPEndPoint + ", expecting from " + expectedRTPEndPoint + ".");
                m_rtpJobs.Add(rtpJob.RTPListenEndPoint.Port, rtpJob);

                //ThreadPool.QueueUserWorkItem(delegate { StartRTPListener(rtpJob); });

                Console.WriteLine(rtpJob.LocalSDP.ToString());

                uas.Answer("application/sdp", rtpJob.LocalSDP.ToString(), CallProperties.CreateNewTag(), null, SIPDialogueTransferModesEnum.NotAllowed);

                var hangupTimer = new Timer(delegate
                {
                    if (!rtpJob.StopJob)
                    {
                        if (uas != null && uas.SIPDialogue != null)
                        {
                            if (rtpJob.RTPPacketReceived && !rtpJob.ErrorOnRTPSend)
                            {
                                Publish(sipRequest.URI.User, "Test completed. There were no RTP send or receive errors.");
                            }
                            else if (!rtpJob.RTPPacketReceived)
                            {
                                Publish(sipRequest.URI.User, "Test completed. An error was identified, no RTP packets were received.");
                            }
                            else
                            {
                                Publish(sipRequest.URI.User, "Test completed. An error was identified, there was a problem when attempting to send an RTP packet.");
                            }
                            rtpJob.Stop();
                            uas.SIPDialogue.Hangup(m_sipTransport, null);
                        }
                    }
                }, null, HANGUP_TIMEOUT, Timeout.Infinite);
            }
            else if (sipRequest.Method == SIPMethodsEnum.CANCEL)
            {
                UASInviteTransaction inviteTransaction = (UASInviteTransaction)m_sipTransport.GetTransaction(SIPTransaction.GetRequestTransactionId(sipRequest.Header.Vias.TopViaHeader.Branch, SIPMethodsEnum.INVITE));

                if (inviteTransaction != null)
                {
                    Console.WriteLine("Matching CANCEL request received " + sipRequest.URI.ToString() + ".\n");
                    Publish(sipRequest.URI.User, "CANCEL request received from " + remoteEndPoint + " for " + sipRequest.URI.ToString() + ".");
                    SIPCancelTransaction cancelTransaction = m_sipTransport.CreateCancelTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, inviteTransaction);
                    cancelTransaction.GotRequest(localSIPEndPoint, remoteEndPoint, sipRequest);
                }
                else
                {
                    Console.WriteLine("No matching transaction was found for CANCEL to " + sipRequest.URI.ToString() + ".\n");
                    SIPResponse noCallLegResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.CallLegTransactionDoesNotExist, null);
                    m_sipTransport.SendResponse(noCallLegResponse);
                }
            }
            else
            {
                Console.WriteLine("SIP " + sipRequest.Method + " request received but no processing has been set up for it, rejecting.\n");
                Publish(sipRequest.URI.User, sipRequest.Method + " request received from " + remoteEndPoint + " for " + sipRequest.URI.ToString() + ".");
                SIPResponse notAllowedResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null);
                m_sipTransport.SendResponse(notAllowedResponse);
            }
        }
Example #25
0
        static void Main()
        {
            Console.WriteLine("SIPSorcery client user agent example.");
            Console.WriteLine("Press ctrl-c to 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)));

            // Un/comment this line to see/hide each SIP message sent and received.
            EnableTraceLogs(sipTransport);

            // To keep things a bit simpler this example only supports a single call at a time and the SIP server user agent
            // acts as a singleton
            SIPUserAgent            userAgent = new SIPUserAgent(sipTransport, null);
            CancellationTokenSource rtpCts    = null; // Cancellation token to stop the RTP stream.
            Socket rtpSocket     = null;
            Socket controlSocket = null;

            // Because this is a server user agent the SIP transport must start listening for client user agents.
            sipTransport.SIPTransportRequestReceived += (SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) =>
            {
                try
                {
                    if (sipRequest.Header.From != null &&
                        sipRequest.Header.From.FromTag != null &&
                        sipRequest.Header.To != null &&
                        sipRequest.Header.To.ToTag != null)
                    {
                        userAgent.InDialogRequestReceivedAsync(sipRequest).Wait();
                    }
                    if (sipRequest.Method == SIPMethodsEnum.INVITE)
                    {
                        SIPSorcery.Sys.Log.Logger.LogInformation($"Incoming call request: {localSIPEndPoint}<-{remoteEndPoint} {sipRequest.URI}.");

                        // Check there's a codec we support in the INVITE offer.
                        var        offerSdp       = SDP.ParseSDPDescription(sipRequest.Body);
                        IPEndPoint dstRtpEndPoint = SDP.GetSDPRTPEndPoint(sipRequest.Body);
                        RTPSession rtpSession     = null;
                        string     audioFile      = null;

                        if (offerSdp.Media.Any(x => x.Media == SDPMediaTypesEnum.audio && x.HasMediaFormat((int)RTPPayloadTypesEnum.PCMU)))
                        {
                            Log.LogDebug($"Using PCMU RTP media type and audio file {AUDIO_FILE_PCMU}.");
                            rtpSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);
                            audioFile  = AUDIO_FILE_PCMU;
                        }

                        if (rtpSession == null)
                        {
                            // Didn't get a match on the codecs we support.
                            SIPResponse noMatchingCodecResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.NotAcceptableHere, null);
                            sipTransport.SendResponse(noMatchingCodecResponse);
                        }
                        else
                        {
                            // If there's already a call in progress hang it up. Of course this is not ideal for a real softphone or server but it
                            // means this example can be kept simpler.
                            if (userAgent?.IsAnswered == true)
                            {
                                userAgent?.Hangup();
                            }
                            rtpCts?.Cancel();

                            UASInviteTransaction uasTransaction = sipTransport.CreateUASTransaction(sipRequest, null);
                            if (userAgent.AcceptCall(uasTransaction))
                            {
                                rtpCts = new CancellationTokenSource();

                                // The RTP socket is listening on IPAddress.Any but the IP address placed into the SDP needs to be one the caller can reach.
                                IPAddress rtpAddress = NetServices.GetLocalAddressForRemote(dstRtpEndPoint.Address);
                                // Initialise an RTP session to receive the RTP packets from the remote SIP server.
                                NetServices.CreateRtpSocket(rtpAddress, RTP_PORT_START, RTP_PORT_END, false, out rtpSocket, out controlSocket);

                                var rtpRecvSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);
                                var rtpSendSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);
                                rtpSendSession.DestinationEndPoint           = dstRtpEndPoint;
                                rtpRecvSession.OnReceiveFromEndPointChanged += (oldEP, newEP) =>
                                {
                                    Log.LogDebug($"RTP destination end point changed from {oldEP} to {newEP}.");
                                    rtpSendSession.DestinationEndPoint = newEP;
                                };

                                Task.Run(() => RecvRtp(rtpSocket, rtpRecvSession, rtpCts));
                                Task.Run(() => SendRtp(rtpSocket, rtpSendSession, rtpCts));

                                userAgent.Answer(GetSDP(rtpSocket.LocalEndPoint as IPEndPoint));
                            }
                        }
                    }
                    else if (sipRequest.Method == SIPMethodsEnum.SUBSCRIBE)
                    {
                        SIPResponse notAllowededResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.MethodNotAllowed, null);
                        sipTransport.SendResponse(notAllowededResponse);
                    }
                    else if (sipRequest.Method == SIPMethodsEnum.OPTIONS || sipRequest.Method == SIPMethodsEnum.REGISTER)
                    {
                        SIPResponse optionsResponse = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                        sipTransport.SendResponse(optionsResponse);
                    }
                }
                catch (Exception reqExcp)
                {
                    SIPSorcery.Sys.Log.Logger.LogWarning($"Exception handling {sipRequest.Method}. {reqExcp.Message}");
                }
            };

            // Ctrl-c will gracefully exit the call at any point.
            Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
            {
                e.Cancel = true;
                exitCts.Cancel();
                rtpCts?.Cancel();
            };

            // 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')
                        {
                            // Initiate a transfer.
                            bool transferResult = await userAgent.Transfer(SIPURI.ParseSIPURI(TRANSFER_DESTINATION_SIP_URI), new TimeSpan(0, 0, TRANSFER_TIMEOUT_SECONDS), exitCts.Token);
                            if (transferResult)
                            {
                                // If the transfer was accepted the original call will already have been hungup.
                                userAgent = null;
                                exitCts.Cancel();
                            }
                            else
                            {
                                Log.LogWarning($"Transfer to {TRANSFER_DESTINATION_SIP_URI} failed.");
                            }
                        }
                        else if (keyProps.KeyChar == 'q')
                        {
                            // Quit application.
                            exitCts.Cancel();
                        }
                    }
                }
                catch (Exception excp)
                {
                    Log.LogError($"Exception Key Press listener. {excp.Message}.");
                }
            });

            // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed.
            exitCts.Token.WaitHandle.WaitOne();

            Log.LogInformation("Exiting...");

            rtpSocket?.Close();
            controlSocket?.Close();

            if (userAgent != null)
            {
                if (userAgent.IsAnswered)
                {
                    Log.LogInformation($"Hanging up call to {userAgent?.CallDescriptor?.To}.");
                    userAgent.Hangup();
                }

                // Give the final 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();
            }
        }
Example #26
0
        static void Main(string[] args)
        {
            Console.WriteLine("SIPSorcery client user agent example.");
            Console.WriteLine("Press ctrl-c to exit.");

            // Plumbing code to facilitate a graceful exit.
            CancellationTokenSource rtpCts = new CancellationTokenSource(); // Cancellation token to stop the RTP stream.
            bool isCallHungup  = false;
            bool hasCallFailed = false;

            // Logging configuration. Can be ommitted if internal SIPSorcery debug and warning messages are not required.
            var loggerFactory = new Microsoft.Extensions.Logging.LoggerFactory();
            var loggerConfig  = new LoggerConfiguration()
                                .Enrich.FromLogContext()
                                .MinimumLevel.Is(Serilog.Events.LogEventLevel.Debug)
                                .WriteTo.Console()
                                .CreateLogger();

            loggerFactory.AddSerilog(loggerConfig);
            SIPSorcery.Sys.Log.LoggerFactory = loggerFactory;

            SIPURI callUri = SIPURI.ParseSIPURI(DEFAULT_DESTINATION_SIP_URI);

            if (args != null && args.Length > 0)
            {
                if (!SIPURI.TryParse(args[0]))
                {
                    Log.LogWarning($"Command line argument could not be parsed as a SIP URI {args[0]}");
                }
                else
                {
                    callUri = SIPURI.ParseSIPURIRelaxed(args[0]);
                }
            }

            Log.LogInformation($"Call destination {callUri}.");

            // Set up a default SIP transport.
            var       sipTransport = new SIPTransport();
            int       port         = SIPConstants.DEFAULT_SIP_PORT + 1000;
            IPAddress localAddress = sipTransport.GetLocalAddress(IPAddress.Parse("8.8.8.8"));

            sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(localAddress, port)));
            //sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, port)));
            //sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.IPv6Any, port)));

            //EnableTraceLogs(sipTransport);

            // Select the IP address to use for RTP based on the destination SIP URI.
            var endPointForCall = callUri.ToSIPEndPoint() == null?sipTransport.GetDefaultSIPEndPoint(callUri.Protocol) : sipTransport.GetDefaultSIPEndPoint(callUri.ToSIPEndPoint());

            // Initialise an RTP session to receive the RTP packets from the remote SIP server.
            Socket rtpSocket     = null;
            Socket controlSocket = null;
            // TODO (find something better): If the SIP endpoint is using 0.0.0.0 for SIP use loopback for RTP.
            IPAddress rtpAddress = localAddress;

            NetServices.CreateRtpSocket(rtpAddress, 49000, 49100, false, out rtpSocket, out controlSocket);
            var rtpSendSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);

            // Create a client user agent to place a call to a remote SIP server along with event handlers for the different stages of the call.
            var uac = new SIPClientUserAgent(sipTransport);

            uac.CallTrying += (uac, resp) =>
            {
                Log.LogInformation($"{uac.CallDescriptor.To} Trying: {resp.StatusCode} {resp.ReasonPhrase}.");
            };
            uac.CallRinging += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Ringing: {resp.StatusCode} {resp.ReasonPhrase}.");
            uac.CallFailed  += (uac, err) =>
            {
                Log.LogWarning($"{uac.CallDescriptor.To} Failed: {err}");
                hasCallFailed = true;
            };
            uac.CallAnswered += (uac, resp) =>
            {
                if (resp.Status == SIPResponseStatusCodesEnum.Ok)
                {
                    Log.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");

                    IPEndPoint remoteRtpEndPoint = SDP.GetSDPRTPEndPoint(resp.Body);

                    Log.LogDebug($"Sending initial RTP packet to remote RTP socket {remoteRtpEndPoint}.");

                    // Send a dummy packet to open the NAT session on the RTP path.
                    rtpSendSession.SendAudioFrame(rtpSocket, remoteRtpEndPoint, 0, new byte[] { 0x00 });
                }
                else
                {
                    Log.LogWarning($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");
                }
            };

            // The only incoming request that needs to be explicitly handled for this example is if the remote end hangs up the call.
            sipTransport.SIPTransportRequestReceived += (SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) =>
            {
                if (sipRequest.Method == SIPMethodsEnum.BYE)
                {
                    SIPNonInviteTransaction byeTransaction = sipTransport.CreateNonInviteTransaction(sipRequest, remoteEndPoint, localSIPEndPoint, null);
                    SIPResponse             byeResponse    = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                    byeTransaction.SendFinalResponse(byeResponse);

                    if (uac.IsUACAnswered)
                    {
                        Log.LogInformation("Call was hungup by remote server.");
                        isCallHungup = true;
                        rtpCts.Cancel();
                    }
                }
            };

            // It's a good idea to start the RTP receiving socket before the call request is sent.
            // A SIP server will generally start sending RTP as soon as it has processed the incoming call request and
            // being ready to receive will stop any ICMP error response being generated.
            Task.Run(() => SendRecvRtp(rtpSocket, rtpSendSession, rtpCts));

            // Start the thread that places the call.
            SIPCallDescriptor callDescriptor = new SIPCallDescriptor(
                SIPConstants.SIP_DEFAULT_USERNAME,
                null,
                callUri.ToString(),
                SIPConstants.SIP_DEFAULT_FROMURI,
                null, null, null, null,
                SIPCallDirection.Out,
                SDP.SDP_MIME_CONTENTTYPE,
                GetSDP(rtpSocket.LocalEndPoint as IPEndPoint).ToString(),
                null);

            uac.Call(callDescriptor);

            // Ctrl-c will gracefully exit the call at any point.
            Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
            {
                e.Cancel = true;
                rtpCts.Cancel();
            };

            // At this point the call has been initiated and everything will be handled in an event handler or on the RTP
            // receive task. The code below is to gracefully exit.

            // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed.
            rtpCts.Token.WaitHandle.WaitOne();

            Log.LogInformation("Exiting...");

            rtpSocket?.Close();
            controlSocket?.Close();

            if (!isCallHungup && uac != null)
            {
                if (uac.IsUACAnswered)
                {
                    Log.LogInformation($"Hanging up call to {uac.CallDescriptor.To}.");
                    uac.Hangup();
                }
                else if (!hasCallFailed)
                {
                    Log.LogInformation($"Cancelling call to {uac.CallDescriptor.To}.");
                    uac.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();
            }
        }
Example #27
0
        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 trnasport and RTP stream.
            bool isCallHungup  = false;
            bool hasCallFailed = false;

            AddConsoleLogger();

            SIPURI callUri = SIPURI.ParseSIPURI(DEFAULT_DESTINATION_SIP_URI);

            Log.LogInformation($"Call destination {callUri}.");

            // Set up a default SIP transport.
            var sipTransport = new SIPTransport();

            sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, 0)));

            EnableTraceLogs(sipTransport);

            var lookupResult = SIPDNSManager.ResolveSIPService(callUri, false);

            Log.LogDebug($"DNS lookup result for {callUri}: {lookupResult?.GetSIPEndPoint()}.");
            var dstAddress = lookupResult.GetSIPEndPoint().Address;

            IPAddress localIPAddress = NetServices.GetLocalAddressForRemote(dstAddress);

            // Initialise an RTP session to receive the RTP packets from the remote SIP server.
            Socket rtpSocket     = null;
            Socket controlSocket = null;

            NetServices.CreateRtpSocket(localIPAddress, 48000, 48100, false, out rtpSocket, out controlSocket);
            var rtpRecvSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);
            var rtpSendSession = new RTPSession((int)RTPPayloadTypesEnum.PCMU, null, null);

            // Create a client user agent to place a call to a remote SIP server along with event handlers for the different stages of the call.
            var uac = new SIPClientUserAgent(sipTransport);

            uac.CallTrying += (uac, resp) =>
            {
                Log.LogInformation($"{uac.CallDescriptor.To} Trying: {resp.StatusCode} {resp.ReasonPhrase}.");
            };
            uac.CallRinging += (uac, resp) => Log.LogInformation($"{uac.CallDescriptor.To} Ringing: {resp.StatusCode} {resp.ReasonPhrase}.");
            uac.CallFailed  += (uac, err) =>
            {
                Log.LogWarning($"{uac.CallDescriptor.To} Failed: {err}");
                hasCallFailed = true;
            };
            uac.CallAnswered += (uac, resp) =>
            {
                if (resp.Status == SIPResponseStatusCodesEnum.Ok)
                {
                    Log.LogInformation($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");

                    // Only set the remote RTP end point if there hasn't already been a packet received on it.
                    if (_remoteRtpEndPoint == null)
                    {
                        _remoteRtpEndPoint = SDP.GetSDPRTPEndPoint(resp.Body);
                        Log.LogDebug($"Remote RTP socket {_remoteRtpEndPoint}.");
                    }
                }
                else
                {
                    Log.LogWarning($"{uac.CallDescriptor.To} Answered: {resp.StatusCode} {resp.ReasonPhrase}.");
                }
            };

            // The only incoming request that needs to be explicitly handled for this example is if the remote end hangs up the call.
            sipTransport.SIPTransportRequestReceived += (SIPEndPoint localSIPEndPoint, SIPEndPoint remoteEndPoint, SIPRequest sipRequest) =>
            {
                if (sipRequest.Method == SIPMethodsEnum.BYE)
                {
                    SIPNonInviteTransaction byeTransaction = sipTransport.CreateNonInviteTransaction(sipRequest, null);
                    SIPResponse             byeResponse    = SIPTransport.GetResponse(sipRequest, SIPResponseStatusCodesEnum.Ok, null);
                    byeTransaction.SendFinalResponse(byeResponse);

                    if (uac.IsUACAnswered)
                    {
                        Log.LogInformation("Call was hungup by remote server.");
                        isCallHungup = true;
                        exitCts.Cancel();
                    }
                }
            };

            // It's a good idea to start the RTP receiving socket before the call request is sent.
            // A SIP server will generally start sending RTP as soon as it has processed the incoming call request and
            // being ready to receive will stop any ICMP error response being generated.
            Task.Run(() => RecvRtp(rtpSocket, rtpRecvSession, exitCts));
            Task.Run(() => SendRtp(rtpSocket, rtpSendSession, exitCts));

            // Start the thread that places the call.
            SIPCallDescriptor callDescriptor = new SIPCallDescriptor(
                SIP_USERNAME,
                SIP_PASSWORD,
                callUri.ToString(),
                $"sip:{SIP_USERNAME}@localhost",
                callUri.CanonicalAddress,
                null, null, null,
                SIPCallDirection.Out,
                SDP.SDP_MIME_CONTENTTYPE,
                GetSDP(rtpSocket.LocalEndPoint as IPEndPoint).ToString(),
                null);

            uac.Call(callDescriptor);

            // Ctrl-c will gracefully exit the call at any point.
            Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
            {
                e.Cancel = true;
                exitCts.Cancel();
            };

            // At this point the call has been initiated and everything will be handled in an event handler.

            Task.Run(() =>
            {
                try
                {
                    while (!exitCts.Token.WaitHandle.WaitOne(0))
                    {
                        var keyProps = Console.ReadKey();
                        if (keyProps.KeyChar == 'h')
                        {
                        }
                        else if (keyProps.KeyChar == 'q')
                        {
                            Console.WriteLine();
                            Console.WriteLine("Hangup requested by user...");

                            uac.Hangup();

                            exitCts.Cancel();
                            rtpSocket?.Close();
                            controlSocket?.Close();

                            SIPSorcery.Sys.Log.Logger.LogInformation("Quitting...");

                            if (sipTransport != null)
                            {
                                SIPSorcery.Sys.Log.Logger.LogInformation("Shutting down SIP transport...");
                                sipTransport.Shutdown();
                            }
                        }
                    }
                }
                catch (Exception excp)
                {
                    SIPSorcery.Sys.Log.Logger.LogError($"Exception Key Press listener. {excp.Message}.");
                }
            });

            // Wait for a signal saying the call failed, was cancelled with ctrl-c or completed.
            exitCts.Token.WaitHandle.WaitOne();

            Log.LogInformation("Exiting...");

            rtpSocket?.Close();
            controlSocket?.Close();

            if (!isCallHungup && uac != null)
            {
                if (uac.IsUACAnswered)
                {
                    Log.LogInformation($"Hanging up call to {uac.CallDescriptor.To}.");
                    uac.Hangup();
                }
                else if (!hasCallFailed)
                {
                    Log.LogInformation($"Cancelling call to {uac.CallDescriptor.To}.");
                    uac.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();
            }
        }