A RFC 3261/3262 compliant SIP stack written in Rust. The goal of this project is to provide a high-performance, reliable, and easy-to-use SIP stack that can be used in various scenarios.
Features
RFC 3261/3262 Compliant: Full compliance with SIP specification
Multiple Transport Support: UDP, TCP, TLS, WebSocket (TLS/WebSocket require the rustls and websocket features, enabled by default). The TLS client goes through a backend seam (platform::tls) — host default is the built-in rustls; embedded stacks can register their own connector
Transaction Layer: Complete SIP transaction state machine
Dialog Layer: SIP dialog management
Reliable Provisionals: PRACK (RFC 3262 / 100rel) support
Digest Authentication: Built-in authentication support
High Performance: Built with Rust for maximum performance
Easy to Use: Simple and intuitive API design
no_std / Embedded Support
rsipstack compiles without std (alloc-only) for embassy-based targets —
verified on xtensa-esp32s3-none-elf (ESP32-S3):
Core SIP codec, transaction, dialog, and UDP transport layers are
no_std + alloc.
The platform-embassy backend maps the runtime seams onto
embassy-time / embassy-sync (critical-section based); task spawning is
injected once at startup:
Concurrent waits use the backend-agnostic select2 / select3 helpers
instead of tokio::select!.
SIPS (SIP over TLS, client role) goes through the
platform::tls::TlsConnector seam: register a backend with
platform::tls::set_client_connector and every
TlsConnection::connect rides it. Host builds without a registered
connector keep using the built-in rustls path.
TCP / WebSocket transports still require platform-tokio; on embedded,
use UDP or SIPS today.
On no_std, register a DomainResolver instead of the tokio DNS resolver.
TODO
Transport support
UDP
TCP
TLS
WebSocket
Digest Authentication
Transaction Layer
Dialog Layer
WASM target
no_std embassy-based targets
Use Cases
This SIP stack can be used in various scenarios, including but not limited to:
Integration with WebRTC for browser-based communication, such as WebRTC SBC.
Building custom SIP proxies or registrars
Building custom SIP user agents (SIP.js alternative)
Why Rust?
We are a group of developers who are passionate about SIP and Rust. We believe that Rust is a great language for building high-performance network applications, and we want to bring the power of Rust to the SIP/WebRTC/SFU world.
Quick Start Examples
SIP Proxy Server
A stateful SIP proxy that routes calls between registered users:
# Run proxy server
cargo run --example proxy -- --port 25060 --addr 127.0.0.1
# Run with external IP
cargo run --example proxy -- --port 25060 --external-ip 1.2.3.4
This example demonstrates:
SIP user registration and location service
Call routing between registered users
Transaction forwarding and response handling
Session management for active calls
Handling INVITE, BYE, REGISTER, and ACK methods
SIP User Agent Client
A complete SIP client with registration, calling, and media support:
# Local demo proxy
cargo run --example client -- --port 25061 --sip-server 127.0.0.1:25060 --auto-answer
# Register with a SIP server
cargo run --example client -- --sip-server sip.example.com --user alice --password secret --auto-answer
API Usage Guide
1. Simple SIP Connection
use rsipstack::sip::{HostWithPort, Transport};
use rsipstack::transport::{udp::UdpConnection, SipAddr};
use tokio_util::sync::CancellationToken;
// Create UDP connection bound to an ephemeral local port
let cancel_token = CancellationToken::new();
let connection = UdpConnection::create_connection(
"0.0.0.0:0".parse()?,
None,
Some(cancel_token.child_token()),
)
.await?;
// Prepare the remote target
let target_addr = SipAddr::new(
Transport::Udp,
HostWithPort::try_from("127.0.0.1:5060")?,
);
// Send raw SIP message
let sip_message = "OPTIONS sip:test@example.com SIP/2.0\r\n...";
connection
.send_raw(sip_message.as_bytes(), &target_addr)
.await?;
2. Using Transport Listeners
use rsipstack::transport::{
TcpListenerConnection, TransportLayer,
};
use tokio_util::sync::CancellationToken;
// Build a transport layer and register listeners
let cancel_token = CancellationToken::new();
let transport_layer = TransportLayer::new(cancel_token.clone());
let tcp_listener = TcpListenerConnection::new(
"0.0.0.0:5060".parse()?,
None,
)
.await?;
transport_layer.add_transport(tcp_listener.into());
To add TLS or WebSocket listeners, construct a TlsListenerConnection or
WebSocketListenerConnection and register it with transport_layer.add_transport(...).
rustls crypto provider. rsipstack’s TLS uses rustls’s process-wide crypto
provider and builds rustls with ring. If your application also links the
aws-lc-rs backend (for example through reqwest‘s default rustls setup),
rustls cannot pick one on its own and the first TLS connection panics with
“Could not automatically determine the process-level CryptoProvider”.
Install a provider once at startup, before creating any TLS transport:
The installed provider is used for every rustls user in the process,
rsipstack included, so it is also where to restrict cipher suites.
The raw TransportEvent stream is an internal API consumed by Endpoint::serve.
To process incoming SIP messages, build an Endpoint around the transport layer and
drive it with endpoint.serve() while consuming transactions via
endpoint.incoming_transactions() (see the next section).
3. Using Endpoint and Transactions
use rsipstack::sip::{Method, StatusCode};
use rsipstack::{EndpointBuilder, transport::TransportLayer};
use tokio_util::sync::CancellationToken;
// Build endpoint with transport layer
let cancel_token = CancellationToken::new();
let transport_layer = TransportLayer::new(cancel_token.clone());
let endpoint = EndpointBuilder::new()
.with_transport_layer(transport_layer)
.with_cancel_token(cancel_token.clone())
.build();
// Start endpoint background task
let endpoint_inner = endpoint.inner.clone();
tokio::spawn(async move {
if let Err(err) = endpoint_inner.serve().await {
eprintln!("endpoint stopped: {err}");
}
});
// Handle incoming transactions
let mut incoming = endpoint
.incoming_transactions()
.expect("transaction receiver available");
while let Some(transaction) = incoming.recv().await {
// Process transaction based on method
match transaction.original.method {
Method::Register => {
transaction.reply(StatusCode::OK).await?;
}
Method::Options => {
transaction.reply(StatusCode::OK).await?;
}
// ... handle other methods
}
}
4. Creating a User Agent Client
use rsipstack::dialog::dialog_layer::DialogLayer;
use rsipstack::dialog::registration::Registration;
use rsipstack::dialog::authenticate::Credential;
use rsipstack::dialog::invitation::InviteOption;
use std::sync::Arc;
// Create dialog layer
let dialog_layer = Arc::new(DialogLayer::new(endpoint.inner.clone()));
// Register with server
let credential = Credential {
username: "alice".to_string(),
password: "secret".to_string(),
realm: None,
auth_username: None,
};
let mut registration = Registration::new(endpoint.inner.clone(), Some(credential.clone()));
let response = registration.register("sip:registrar.example.com".parse()?, None).await?;
// Make outgoing call
let invite_option = InviteOption {
callee: "sip:bob@example.com".parse()?,
caller: "sip:alice@example.com".parse()?,
content_type: None,
offer: None,
contact: "sip:alice@192.168.1.100:5060".parse()?,
credential: Some(credential),
headers: None,
..Default::default()
};
// Create the dialog state channel (drives `process_dialog`-style loops)
let (state_sender, _state_receiver) = dialog_layer.new_dialog_state_channel();
let (invite_dialog, response) = dialog_layer.do_invite(invite_option, state_sender).await?;
5. Implementing a Proxy
use rsipstack::sip::{Method, StatusCode};
use rsipstack::transaction::{Transaction, key::{TransactionKey, TransactionRole}};
use rsipstack::sip::prelude::HeadersExt;
use std::collections::HashMap;
// Handle incoming requests
while let Some(mut transaction) = incoming.recv().await {
match transaction.original.method {
Method::Register => {
// Store user registration
let user = User::try_from(&transaction.original)?;
users.insert(user.username.clone(), user);
transaction.reply(StatusCode::OK).await?;
}
Method::Invite => {
// Route call to registered user
let callee = transaction.original.to_header()?.uri()?.auth
.map(|a| a.user)
.unwrap_or_default();
if let Some(target) = users.get(&callee) {
// Create new client transaction for forwarding
let mut forwarded_req = transaction.original.clone();
let via = transaction.endpoint_inner.get_via(None, None)?;
forwarded_req.headers.push_front(via.into());
let key = TransactionKey::from_request(&forwarded_req, TransactionRole::Client)?;
let mut forwarded_tx = Transaction::new_client(
key,
forwarded_req,
transaction.endpoint_inner.clone(),
None
);
forwarded_tx.destination = Some(target.destination.clone());
forwarded_tx.send().await?;
} else {
transaction.reply(StatusCode::NotFound).await?;
}
}
// ... handle other methods
}
}
This is a simplified sketch: User, users and incoming are types/values you define yourself
(incoming comes from endpoint.incoming_transactions()?, and User is a struct you implement
that holds the registered user’s username and destination SipAddr). A complete, working proxy
is provided in examples/proxy.rs.
Running Tests
Unit Tests
cargo test
Benchmark Tests
# Run server
cargo run -r --bin bench_ua -- -m server -p 5060
# Run client with 1000 calls
cargo run -r --bin bench_ua -- -m client -p 5061 -s 127.0.0.1:5060 -c 1000
The test monitor:
=== SIP Benchmark UA Stats ===
Dialogs: 9992
Active Calls: 9983
Rejected Calls: 0
Failed Calls: 0
Total Calls: 250276
Calls/Second: 1501
============================
rsipstack - A SIP Stack written in Rust
A RFC 3261/3262 compliant SIP stack written in Rust. The goal of this project is to provide a high-performance, reliable, and easy-to-use SIP stack that can be used in various scenarios.
Features
rustlsandwebsocketfeatures, enabled by default). The TLS client goes through a backend seam (platform::tls) — host default is the built-in rustls; embedded stacks can register their own connectorno_std / Embedded Support
rsipstack compiles without
std(alloc-only) for embassy-based targets — verified onxtensa-esp32s3-none-elf(ESP32-S3):Core SIP codec, transaction, dialog, and UDP transport layers are
no_std+alloc.The
platform-embassybackend maps the runtime seams ontoembassy-time/embassy-sync(critical-section based); task spawning is injected once at startup:Concurrent waits use the backend-agnostic
select2/select3helpers instead oftokio::select!.SIPS (SIP over TLS, client role) goes through the
platform::tls::TlsConnectorseam: register a backend withplatform::tls::set_client_connectorand everyTlsConnection::connectrides it. Host builds without a registered connector keep using the built-in rustls path.TCP / WebSocket transports still require
platform-tokio; on embedded, use UDP or SIPS today.On no_std, register a
DomainResolverinstead of the tokio DNS resolver.TODO
Use Cases
This SIP stack can be used in various scenarios, including but not limited to:
Why Rust?
We are a group of developers who are passionate about SIP and Rust. We believe that Rust is a great language for building high-performance network applications, and we want to bring the power of Rust to the SIP/WebRTC/SFU world.
Quick Start Examples
SIP Proxy Server
A stateful SIP proxy that routes calls between registered users:
This example demonstrates:
SIP User Agent Client
A complete SIP client with registration, calling, and media support:
API Usage Guide
1. Simple SIP Connection
2. Using Transport Listeners
To add TLS or WebSocket listeners, construct a
TlsListenerConnectionorWebSocketListenerConnectionand register it withtransport_layer.add_transport(...).The raw
TransportEventstream is an internal API consumed byEndpoint::serve. To process incoming SIP messages, build anEndpointaround the transport layer and drive it withendpoint.serve()while consuming transactions viaendpoint.incoming_transactions()(see the next section).3. Using Endpoint and Transactions
4. Creating a User Agent Client
5. Implementing a Proxy
This is a simplified sketch:
User,usersandincomingare types/values you define yourself (incomingcomes fromendpoint.incoming_transactions()?, andUseris a struct you implement that holds the registered user’s username and destinationSipAddr). A complete, working proxy is provided inexamples/proxy.rs.Running Tests
Unit Tests
Benchmark Tests
The test monitor:
Documentation
Contributing
We welcome contributions! Please see our Contributing Guide for details.
License
This project is licensed under the MIT License - see the LICENSE file for details.