Skip to content

Commit 6934790

Browse files
authored
Null reference bug fix in SDP packet mangler. (sipsorcery-org#1417)
1 parent 86631d9 commit 6934790

6 files changed

Lines changed: 128 additions & 23 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Stage 1: build
2+
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
3+
WORKDIR /src
4+
5+
# Copy csproj and restore as distinct layers for caching
6+
COPY *.csproj ./
7+
COPY local-nuget/ ./local-nuget/
8+
9+
RUN dotnet restore --source ./local-nuget --source https://api.nuget.org/v3/index.json
10+
11+
# Copy rest of source and publish
12+
COPY . .
13+
RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false
14+
15+
# Stage 2: runtime
16+
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS runtime
17+
WORKDIR /app
18+
19+
# Copy published app
20+
COPY --from=build /app/publish ./
21+
22+
# Expose SIP ports
23+
EXPOSE 5060/udp
24+
EXPOSE 5061/tcp
25+
26+
# Run the server
27+
ENTRYPOINT ["dotnet", "SIPCallServer.dll"]

examples/SIPExamples/SIPCallServer/Program.cs

Lines changed: 85 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ public SendSilenceJob(Timer timer, SIPUserAgent ua)
6464

6565
class Program
6666
{
67+
private const string PUBLIC_IP_ADDRESS_ENV_VAR = "PUBLIC_IP_ADDRESS";
68+
private const string RTP_PORT_ENV_VAR = "RTP_PORT";
69+
private const string IS_HEADLESS_ENV_VAR = "IS_HEADLESS";
70+
6771
//private static string DEFAULT_CALL_DESTINATION = "sip:*61@192.168.0.48";
6872
private static string DEFAULT_CALL_DESTINATION = "sip:aaron@127.0.0.1:7060;transport=tcp";
6973
private static string DEFAULT_TRANSFER_DESTINATION = "sip:*61@192.168.0.48";
@@ -93,38 +97,85 @@ class Program
9397
/// </summary>
9498
private static ConcurrentDictionary<string, SIPRegistrationUserAgent> _registrations = new ConcurrentDictionary<string, SIPRegistrationUserAgent>();
9599

100+
private static string _publicIPAddress = null;
101+
private static int _rtpPort = 0;
102+
103+
/// <summary>
104+
/// If running on K8S or similar there is no console window to receive key presses.
105+
/// </summary>
106+
private static bool _isHeadless = false;
107+
96108
static async Task Main()
97109
{
98110
Console.WriteLine("SIPSorcery SIP Call Server example.");
99-
Console.WriteLine("Press 'c' to place a call to the default destination.");
100-
Console.WriteLine("Press 'd' to send a random DTMF tone to the newest call.");
101-
Console.WriteLine("Press 'h' to hangup the oldest call.");
102-
Console.WriteLine("Press 'H' to hangup all calls.");
103-
Console.WriteLine("Press 'l' to list current calls.");
104-
Console.WriteLine("Press 'r' to list current registrations.");
105-
Console.WriteLine("Press 't' to transfer the newest call to the default destination.");
106-
Console.WriteLine("Press 'q' to quit.");
111+
112+
if (!_isHeadless)
113+
{
114+
Console.WriteLine("Press 'c' to place a call to the default destination.");
115+
Console.WriteLine("Press 'd' to send a random DTMF tone to the newest call.");
116+
Console.WriteLine("Press 'h' to hangup the oldest call.");
117+
Console.WriteLine("Press 'H' to hangup all calls.");
118+
Console.WriteLine("Press 'l' to list current calls.");
119+
Console.WriteLine("Press 'r' to list current registrations.");
120+
Console.WriteLine("Press 't' to transfer the newest call to the default destination.");
121+
Console.WriteLine("Press 'q' to quit.");
122+
}
107123

108124
Log = AddConsoleLogger();
109125

110126
// Set up a default SIP transport.
111127
_sipTransport = new SIPTransport();
128+
129+
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(PUBLIC_IP_ADDRESS_ENV_VAR)))
130+
{
131+
_publicIPAddress = Environment.GetEnvironmentVariable(PUBLIC_IP_ADDRESS_ENV_VAR);
132+
_sipTransport.ContactHost = _publicIPAddress;
133+
}
134+
135+
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(RTP_PORT_ENV_VAR)))
136+
{
137+
int.TryParse(Environment.GetEnvironmentVariable(RTP_PORT_ENV_VAR), out _rtpPort);
138+
}
139+
140+
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(IS_HEADLESS_ENV_VAR)))
141+
{
142+
bool.TryParse(Environment.GetEnvironmentVariable(IS_HEADLESS_ENV_VAR), out _isHeadless);
143+
}
144+
112145
_sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT)));
113146
_sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.IPv6Any, SIP_LISTEN_PORT)));
114147
_sipTransport.AddSIPChannel(new SIPTCPChannel(new IPEndPoint(IPAddress.Any, SIP_LISTEN_PORT)));
115148
var localhostCertificate = new X509Certificate2(SIPS_CERTIFICATE_PATH);
116149
_sipTransport.AddSIPChannel(new SIPTLSChannel(localhostCertificate, new IPEndPoint(IPAddress.Any, SIPS_LISTEN_PORT)));
117150
// If it's desired to listen on a single IP address use the equivalent of:
118151
//_sipTransport.AddSIPChannel(new SIPUDPChannel(new IPEndPoint(IPAddress.Parse("192.168.11.50"), SIP_LISTEN_PORT)));
119-
_sipTransport.EnableTraceLogs();
152+
//_sipTransport.EnableTraceLogs();
120153

121154
_sipTransport.SIPTransportRequestReceived += OnRequest;
122155

123156
// Uncomment to enable registrations.
124157
//StartRegistrations(_sipTransport, _sipAccounts);
125158

126-
CancellationTokenSource exitCts = new CancellationTokenSource();
127-
await Task.Run(() => OnKeyPress(exitCts.Token));
159+
if (!_isHeadless)
160+
{
161+
CancellationTokenSource exitCts = new CancellationTokenSource();
162+
await Task.Run(() => OnKeyPress(exitCts.Token));
163+
164+
Console.WriteLine("Press ctrl-c to exit.");
165+
}
166+
else
167+
{
168+
// Ctrl-c will gracefully exit the call at any point.
169+
ManualResetEvent exitMre = new ManualResetEvent(false);
170+
Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e)
171+
{
172+
e.Cancel = true;
173+
exitMre.Set();
174+
};
175+
176+
// Wait for a signal saying the call failed, was cancelled with ctrl-c or completed.
177+
exitMre.WaitOne();
178+
}
128179

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

@@ -159,7 +210,7 @@ private static async Task OnKeyPress(CancellationToken exit)
159210
ua.OnRtpEvent += (evt, hdr) => Log.LogDebug($"rtp event {evt.EventID}, duration {evt.Duration}, end of event {evt.EndOfEvent}, timestamp {hdr.Timestamp}, marker {hdr.MarkerBit}.");
160211
ua.OnCallHungup += OnHangup;
161212

162-
var rtpSession = CreateRtpSession(ua, null);
213+
var rtpSession = CreateRtpSession(ua, null, _rtpPort);
163214
var callResult = await ua.Call(DEFAULT_CALL_DESTINATION, null, null, rtpSession);
164215

165216
if (callResult)
@@ -297,7 +348,7 @@ private static async Task OnKeyPress(CancellationToken exit)
297348
/// <param name="dst">THe destination specified on an incoming call. Can be used to
298349
/// set the audio source.</param>
299350
/// <returns>A new RTP session object.</returns>
300-
private static VoIPMediaSession CreateRtpSession(SIPUserAgent ua, string dst)
351+
private static VoIPMediaSession CreateRtpSession(SIPUserAgent ua, string dst, int bindPort)
301352
{
302353
List<AudioCodecsEnum> codecs = new List<AudioCodecsEnum> { AudioCodecsEnum.PCMU, AudioCodecsEnum.PCMA, AudioCodecsEnum.G722 };
303354

@@ -310,12 +361,12 @@ private static VoIPMediaSession CreateRtpSession(SIPUserAgent ua, string dst)
310361
Log.LogInformation($"RTP audio session source set to {audioSource}.");
311362

312363
AudioExtrasSource audioExtrasSource = new AudioExtrasSource(new AudioEncoder(), new AudioSourceOptions { AudioSource = audioSource });
313-
audioExtrasSource.RestrictFormats(formats => codecs.Contains(formats.Codec) );
314-
var rtpAudioSession = new VoIPMediaSession(new MediaEndPoints { AudioSource = audioExtrasSource });
364+
audioExtrasSource.RestrictFormats(formats => codecs.Contains(formats.Codec));
365+
var rtpAudioSession = new VoIPMediaSession(new MediaEndPoints { AudioSource = audioExtrasSource }, bindPort: bindPort);
315366
rtpAudioSession.AcceptRtpFromAny = true;
316367

317368
// Wire up the event handler for RTP packets received from the remote party.
318-
rtpAudioSession.OnRtpPacketReceived += (ep, type, rtp) => OnRtpPacketReceived(ua, type, rtp);
369+
rtpAudioSession.OnRtpPacketReceived += (ep, type, rtp) => OnRtpPacketReceived(ua, ep, type, rtp);
319370
rtpAudioSession.OnTimeout += (mediaType) =>
320371
{
321372
if (ua?.Dialogue != null)
@@ -339,9 +390,10 @@ private static VoIPMediaSession CreateRtpSession(SIPUserAgent ua, string dst)
339390
/// <param name="ua">The SIP user agent associated with the RTP session.</param>
340391
/// <param name="type">The media type of the RTP packet (audio or video).</param>
341392
/// <param name="rtpPacket">The RTP packet received from the remote party.</param>
342-
private static void OnRtpPacketReceived(SIPUserAgent ua, SDPMediaTypesEnum type, RTPPacket rtpPacket)
393+
private static void OnRtpPacketReceived(SIPUserAgent ua, IPEndPoint remoteEp, SDPMediaTypesEnum type, RTPPacket rtpPacket)
343394
{
344395
// The raw audio data is available in rtpPacket.Payload.
396+
//Log.LogTrace($"OnRtpPacketReceived from {remoteEp}.");
345397
}
346398

347399
/// <summary>
@@ -386,14 +438,26 @@ private static async Task OnRequest(SIPEndPoint localSIPEndPoint, SIPEndPoint re
386438
ua.Hangup();
387439
};
388440

441+
//bool wasMangled = false;
442+
//sipRequest.Body = SIPPacketMangler.MangleSDP(sipRequest.Body, remoteEndPoint.Address.ToString(), out wasMangled);
443+
//Log.LogDebug("INVITE was mangled=" + wasMangled + " remote=" + remoteEndPoint.Address.ToString() + ".");
444+
//sipRequest.Header.ContentLength = sipRequest.Body.Length;
445+
389446
var uas = ua.AcceptCall(sipRequest);
390-
var rtpSession = CreateRtpSession(ua, sipRequest.URI.User);
447+
var rtpSession = CreateRtpSession(ua, sipRequest.URI.User, _rtpPort);
391448

392449
// Insert a brief delay to allow testing of the "Ringing" progress response.
393450
// Without the delay the call gets answered before it can be sent.
394451
await Task.Delay(500);
395452

396-
await ua.Answer(uas, rtpSession);
453+
if (!string.IsNullOrWhiteSpace(_publicIPAddress))
454+
{
455+
await ua.Answer(uas, rtpSession, IPAddress.Parse(_publicIPAddress));
456+
}
457+
else
458+
{
459+
await ua.Answer(uas, rtpSession);
460+
}
397461

398462
if (ua.IsCallActive)
399463
{
@@ -434,7 +498,7 @@ private static void OnHangup(SIPDialogue dialogue)
434498
string callID = dialogue.CallId;
435499
if (_calls.ContainsKey(callID))
436500
{
437-
if(_calls.TryRemove(callID, out var ua))
501+
if (_calls.TryRemove(callID, out var ua))
438502
{
439503
// This app only uses each SIP user agent once so here the agent is
440504
// explicitly closed to prevent is responding to any new SIP requests.
@@ -475,7 +539,7 @@ private static Microsoft.Extensions.Logging.ILogger AddConsoleLogger()
475539
{
476540
var serilogLogger = new LoggerConfiguration()
477541
.Enrich.FromLogContext()
478-
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Debug)
542+
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose)
479543
.WriteTo.Console()
480544
.CreateLogger();
481545
var factory = new SerilogLoggerFactory(serilogLogger);

examples/SIPExamples/SIPCallServer/SIPCallServer.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
1010
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
1111
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
12-
<PackageReference Include="System.Net.Security" Version="4.3.2" />
1312
</ItemGroup>
1413

1514
<ItemGroup>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
docker build -t sipsorcery/sipcallserver:0.5 .
2+
docker push sipsorcery/sipcallserver:0.5
3+
4+
docker run -it --rm --name sip-callserver -p 5060:5060/udp -p 5061:5061/tcp sipsorcery/sipcallserver:0.2

src/app/SIPPacketMangler.cs

100644100755
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,16 @@ public static string MangleSDP(string sdpBody, string publicIPAddress, out bool
3838
{
3939
if (sdpBody != null && publicIPAddress != null)
4040
{
41-
IPAddress addr = SDP.GetSDPRTPEndPoint(sdpBody).Address;
41+
var sdpEndPoint = SDP.GetSDPRTPEndPoint(sdpBody);
42+
if(sdpEndPoint == null)
43+
{
44+
logger.LogWarning("SDP mangling failed to find a valid RTP endpoint in the SDP body.");
45+
return sdpBody;
46+
}
47+
4248
//rj2: need to consider publicAddress and IPv6 for mangling
4349
IPAddress pubaddr = IPAddress.Parse(publicIPAddress);
50+
var addr = sdpEndPoint.Address;
4451
string sdpAddress = addr.ToString();
4552

4653
// 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.

src/net/SDP/SDP.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,10 @@ public IPEndPoint GetSDPRTPEndPoint()
901901
{
902902
return new IPEndPoint(IPAddress.Parse(sessionConnection.ConnectionAddress), firstMediaOffer.Port);
903903
}
904+
else if(firstMediaOffer != null && firstMediaOffer.Connection != null)
905+
{
906+
return new IPEndPoint(IPAddress.Parse(firstMediaOffer.Connection.ConnectionAddress), firstMediaOffer.Port);
907+
}
904908
else
905909
{
906910
return null;

0 commit comments

Comments
 (0)