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); } } }
/// <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); } }
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); } }
/// <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(); } }
/// <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); }
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(); }); }
/// <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); } }
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); } }
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."); }
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."); }
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(); } }; }
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(); } }
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(); }
public void Call(string destination) { CreateMediaSockets(SDP.GetSDPRTPEndPoint(m_uas.CallRequest.Body)); m_xmppCall.PlaceCall(destination + "@" + GOOGLE_VOICE_HOST, GetSDPForXMPPRequest()); }
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(); } }
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 }
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(); } }; }
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(); }
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(); } }
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(); } }
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(); } }; }
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); } }
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(); } }
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(); } }
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(); } }