SharpICE 0.0.1

dotnet add package SharpICE --version 0.0.1
                    
NuGet\Install-Package SharpICE -Version 0.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SharpICE" Version="0.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SharpICE" Version="0.0.1" />
                    
Directory.Packages.props
<PackageReference Include="SharpICE" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SharpICE --version 0.0.1
                    
#r "nuget: SharpICE, 0.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SharpICE@0.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SharpICE&version=0.0.1
                    
Install as a Cake Addin
#tool nuget:?package=SharpICE&version=0.0.1
                    
Install as a Cake Tool

SharpICE

STUN, TURN and ICE client and server written in C#, with no native dependencies.

NuGet version NuGet version NuGet version

There are three libraries:

  1. SharpSTUN - STUN client and server
  2. SharpTURN - TURN client and server, built on SharpSTUN
  3. SharpICE - ICE agent, built on SharpSTUN and SharpTURN

Implements the following RFCs:

  1. DNS SRV records RFC2782
  2. Framing RTP and RTCP Packets over Connection-Oriented Transport RFC4571
  3. Test Vectors for Session Traversal Utilities for NAT (STUN) RFC5769
  4. Traversal Using Relays around NAT (TURN) Extensions for TCP Allocations RFC6062
  5. TCP Candidates with Interactive Connectivity Establishment (ICE) RFC6544
  6. Multicast DNS RFC6762
  7. URI Scheme for the Session Traversal Utilities for NAT (STUN) Protocol RFC7064
  8. Traversal Using Relays around NAT (TURN) Uniform Resource Identifiers RFC7065
  9. Session Traversal Utilities for NAT (STUN) Usage for Consent Freshness RFC7675
  10. Multiplexing Scheme Updates for Secure Real-time Transport Protocol (SRTP) Extension for Datagram Transport Layer Security (DTLS) RFC7983
  11. Interactive Connectivity Establishment (ICE): A Protocol for Network Address Translator (NAT) Traversal RFC8445
  12. Session Traversal Utilities for NAT (STUN) RFC8489
  13. Traversal Using Relays around NAT (TURN): Relay Extensions to Session Traversal Utilities for NAT (STUN) RFC8656
  14. Trickle ICE: Incremental Provisioning of Candidates for the Interactive Connectivity Establishment (ICE) Protocol RFC8838
  15. Session Description Protocol (SDP) Offer/Answer Procedures for Interactive Connectivity Establishment (ICE) RFC8839

DTLS and TLS

STUN and TURN can run over UDP, TCP, TLS and DTLS. DTLS and TLS are based upon SharpDTLS by default, which supports DTLS 1.2, DTLS 1.3, TLS 1.2 and TLS 1.3. To use another implementation, set DtlsStack or TlsStack in the client or server options:

  1. DtlsStacks.SharpDtls (default) - DTLS 1.3 and DTLS 1.2
  2. DtlsStacks.BouncyCastle - DTLS 1.2 only
  3. TlsStacks.SharpDtls (default) - TLS 1.3 and TLS 1.2
  4. TlsStacks.BouncyCastle - TLS 1.2 only
  5. TlsStacks.System - .NET's SslStream

With SharpDTLS and BouncyCastle, the server certificate must have an exportable private key.

STUN

Client

Create the STUN client. Username and password are only needed for a server with long-term credentials:

using var client = new StunClient(new StunClientOptions { Username = "user", Password = "pass" });

Connect to the server using a STUN URI. A host without a port is looked up using DNS SRV records and each server is tried in turn:

await client.ConnectAsync("stun:stun.example.org");

Get the address the server sees you at:

IPEndPoint mapped = await client.GetMappedAddressAsync();

Send any other request. When the server asks for credentials, the client sends the request again with them:

StunMessage response = await client.RequestAsync(new StunMessage(StunMethod.Binding, StunMessageClass.Request));

To use TLS, use the "stuns" URI. The server's certificate is checked against the host name:

await client.ConnectAsync("stuns:stun.example.org");

To use DTLS instead of TLS, set UseDtls:

using var client = new StunClient(new StunClientOptions { UseDtls = true });
await client.ConnectAsync("stuns:stun.example.org");

For a single Binding request, you don't need a client at all:

IPEndPoint server = await StunClient.ResolveAsync("stun.l.google.com:19302");
IPEndPoint address = await StunClient.GetMappedAddressAsync(server, TimeSpan.FromSeconds(3));

Server

Create the STUN server. Set Realm and Users to require long-term credentials. Certificate is only needed for TLS and DTLS:

var server = new StunServer(new StunServerOptions
{
    Realm = "example.org",
    Users = new Dictionary<string, string> { ["user"] = "pass" },
    Certificate = certificate,
});

Start the listeners:

server.ListenUdp(new IPEndPoint(IPAddress.Any, 3478));
server.ListenTcp(new IPEndPoint(IPAddress.Any, 3478));
server.ListenTls(new IPEndPoint(IPAddress.Any, 5349));
server.ListenDtls(new IPEndPoint(IPAddress.Any, 5349));

The server answers Binding requests. It supports MD5 and SHA-256 credentials, USERHASH, stateless nonces and ALTERNATE-SERVER redirects. DTLS clients must pass a cookie exchange first, so a spoofed address can't create a connection. Classic STUN (RFC 3489) is not answered.

TURN

Client

Create the TURN client and connect to the server. The transport can be UDP, TCP, TLS or DTLS:

using var turn = new TurnClient(new StunClientOptions { Username = "user", Password = "pass" });
await turn.ConnectAsync("turn:turn.example.org?transport=udp");

Allocate the relayed address. The allocation, permissions and channels are refreshed automatically:

await turn.AllocateAsync();
IPEndPoint relayed = turn.RelayedEndPoint;

Receive data from the peers:

turn.DataReceived += (sender, e) =>
{
    IPEndPoint peer = e.Peer;
    // the data is in e.Buffer, e.Offset, e.Count
};

Send data to a peer. A permission for the peer is created first, then the data is sent in a Send indication:

await turn.SendAsync(peer, data);

Bind a channel to send the data to the peer as ChannelData, which has less overhead:

await turn.BindChannelAsync(peer);

Delete the allocation:

await turn.DeallocateAsync();

TCP Allocations

A TCP allocation relays TCP connections to the peers. Connect to the server over TCP or TLS and allocate:

using var turn = new TurnClient(new StunClientOptions { Username = "user", Password = "pass" });
await turn.ConnectAsync("turn:turn.example.org?transport=tcp");
await turn.AllocateAsync(new TurnAllocateOptions { Tcp = true });

Connect to a peer. The connection is a plain Stream of the peer's bytes:

using var connection = await turn.ConnectPeerAsync(peer);
await connection.Stream.WriteAsync(data, 0, data.Length);

Accept the peers that connect to the relayed address. Only peers with a permission can connect:

await turn.CreatePermissionAsync(peer.Address);
turn.ConnectionAttempt += async (sender, e) =>
{
    using var incoming = await turn.AcceptPeerAsync(e);
    // incoming.Stream
};

Server

Create the TURN server. RelayAddress is the public address of the relays:

var server = new TurnServer(new TurnServerOptions
{
    Stun = new StunServerOptions
    {
        Realm = "example.org",
        Users = new Dictionary<string, string> { ["user"] = "pass" },
        AuthenticateBinding = false,
    },
    RelayAddress = IPAddress.Parse("203.0.113.7"),
});

Start the listeners:

server.ListenUdp(new IPEndPoint(IPAddress.Any, 3478));
server.ListenTcp(new IPEndPoint(IPAddress.Any, 3478));

The server supports UDP and TCP allocations, dual-stack allocations, EVEN-PORT, permissions and channels. Its defaults are safe: credentials are required, the relays only reach public unicast addresses (use AllowPeer to allow others), the server's own host is never reachable through a relay, and allocations and connections are limited per user and per address.

ICE

The ICE agent has one component, as WebRTC uses it: RTP and RTCP are multiplexed and all media is bundled.

Agent

Create the agent with the STUN and TURN servers to gather the candidates from. The offerer is usually controlling, the answerer controlled:

var options = new IceAgentOptions { Role = IceRole.Controlled };
options.StunServers.Add("stun:stun.l.google.com:19302");
options.TurnServers.Add(new IceTurnServer { Uri = "turn:turn.example.org", Username = "user", Password = "pass" });
var agent = new IceAgent(options);

Gather the local candidates and send them to the peer, together with LocalUfrag and LocalPassword, in the session description or trickled one by one:

var candidates = await agent.GatherCandidatesAsync();

Set the peer's credentials and candidates, then start the checks. Signal the end of the peer's candidates once it has no more:

agent.SetRemoteCredentials(remoteUfrag, remotePassword);
agent.AddRemoteCandidate("candidate:1 1 udp 2130706431 192.168.1.10 50000 typ host");
agent.Start();
agent.AddRemoteCandidate("a=end-of-candidates");

Receive the data that is not STUN, such as DTLS, RTP and RTCP:

agent.DataReceived += (sender, e) =>
{
    // the data is in e.Buffer, e.Offset, e.Count
};

Once agent.State is Connected, send data to the peer:

agent.Send(datagram, 0, datagram.Length);

To restart ICE, call Restart and send a new offer with the new LocalUfrag, LocalPassword and candidates. When the peer restarts, pass its new credentials; SetRemoteCredentials returns true:

agent.Restart();
bool restarted = agent.SetRemoteCredentials(newRemoteUfrag, newRemotePassword);

Options

  1. IncludeTcp - gathers ICE-TCP candidates (passive and active), including relayed ones from TURN servers over TCP or TLS. UDP is preferred.
  2. RelayOnly - uses relayed candidates only.
  3. Lite - makes the agent an ICE lite agent: host candidates only and no checks of its own.
  4. PublicAddresses - the public addresses of a 1:1 NAT, such as on a cloud host.
  5. MdnsCandidates - hides the local addresses behind random ".local" names, as browsers do. The peer's ".local" candidates are always resolved using multicast DNS.

The agent also supports peer reflexive candidates, triggered checks, regular nomination, role conflict resolution and consent freshness.

Samples

The samples run against each other on one machine with their default settings:

  1. StunServerSample [port] - STUN server on UDP and TCP, port 3478 by default
  2. StunClientSample [uri] [username] [password] - shows the address the STUN server sees you at, stun:127.0.0.1:3478 by default
  3. TurnServerSample [port] [username] [password] [realm] [relayAddress] - TURN server with UDP and TCP relays, port 3479 by default, user user and password pass
  4. TurnClientSample [uri] [username] [password] [peer] - allocates a relay and sends data through it to an echo peer and back, turn:127.0.0.1:3479 by default
dotnet run --project src/TurnServerSample
dotnet run --project src/TurnClientSample
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 is compatible.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.1 51 11/11/2026