Skip to content
restsendPublic

About

SIP Stack Rust library for building SIP applications

Topics

Resources

Stars

213 stars

Watchers

7 watching

Forks

Repository files navigation

rsipstack - A SIP Stack written in Rust

Ask DeepWiki

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):

cargo check --no-default-features --features platform-embassy
  • 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:

    rsipstack::platform::set_spawn_fn(|fut| spawner.spawn(fut).ok());
  • 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:

rustls::crypto::ring::default_provider()
    .install_default()
    .expect("install rustls crypto provider");
// or: rustls::crypto::aws_lc_rs::default_provider().install_default()

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
============================

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.

About

SIP Stack Rust library for building SIP applications

Topics

Resources

Stars

213 stars

Watchers

7 watching

Forks

Used by

Contributors

Languages