BlogOpenSIPS B2BUA Architecture Documentation
Bố cục bài viết
Trang này
Ngày tạo
10/08/2026
Cập nhật
Lượt xem
6
Đọc
19m
OpenSIPS B2BUA Architecture Documentation
Table of Contents
- Overview
- Architecture
- Component Details
- Configuration Reference
- Database Schema
- Call Flow
- Deployment Guide
- Testing Guide
- Troubleshooting
Overview
What is a B2BUA?
A Back-to-Back User Agent (B2BUA) is a SIP element that acts as both a User Agent Server (UAS) and a User Agent Client (UAC). Unlike a SIP proxy that simply forwards requests, a B2BUA terminates the incoming SIP dialog and initiates a new outgoing dialog, creating two independent call legs.
Why B2BUA vs Proxy?
| Feature | SIP Proxy | B2BUA |
|---|---|---|
| SIP Dialogs | 1 end-to-end | 2 independent |
| Call-ID | Preserved | Different per leg |
| Mid-dialog routing | Via Record-Route | Internal handling |
| Call transfers | Pass-through | Process & re-bridge |
| Media control | Limited | Per-leg manipulation |
| Topology hiding | Limited | Full hiding |
| Call recording | Complex | Native support |
Use Cases
- Call Centers: Full call control, recording, and transfer capabilities
- Session Border Controllers (SBC): Topology hiding and security
- VoIP Gateways: Protocol translation and media manipulation
- PBX Systems: Advanced call features and routing
Architecture
System Components
┌─────────────────────────────────────────────────────────────────────────┐
│ Docker Host Network │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ SIP Phone │ │ SIP Phone │ │ SIPp │ │
│ │ (External) │ │ (External) │ │ (Testing) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └───────────────────┼───────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ OpenSIPS │ :5060 (UDP/TCP) │
│ │ B2BUA │ │
│ │ │ │
│ │ ┌──────────┐ │ │
│ │ │ b2b_logic│ │ │
│ │ │ entities │ │ │
│ │ └──────────┘ │ │
│ └───────┬────────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Asterisk │ │ RTPEngine │ │ MySQL │ │
│ │ :5080 │ │ :2223 │ │ :3306 │ │
│ │ │ │ │ │ │ │
│ │ - IVR │ │ - Media │ │ - location │ │
│ │ - Queue │ │ Relay │ │ - b2b_* │ │
│ │ - Record │ │ - Transcode│ │ - dialog │ │
│ │ - Voicemail│ │ │ │ - acc │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Call Leg Architecture
B2BUA (OpenSIPS)
┌─────────────────────┐
│ │
Caller │ ┌─────────────┐ │ Callee
┌────────┐ Leg A │ │ b2b_logic │ │ Leg B ┌────────┐
│ SIP │◄────────────►│ │ │ │◄────────────►│Asterisk│
│ Phone │ Call-ID: A │ │ Bridges │ │ Call-ID: B │ PBX │
└────────┘ │ │ A ←→ B │ │ └────────┘
│ └─────────────┘ │
│ │
│ ┌─────────────┐ │
│ │ b2b_entities│ │
│ │ │ │
│ │ Server: A │ │
│ │ Client: B │ │
│ └─────────────┘ │
│ │
└─────────────────────┘
Legend:
- Leg A: Server entity (incoming call from SIP phone)
- Leg B: Client entity (outgoing call to Asterisk)
- Each leg has independent: Call-ID, From/To tags, CSeq
Data Flow
1. Initial INVITE Flow:
SIP Phone OpenSIPS B2BUA Asterisk
│ │ │
│─────INVITE A──────►│ │
│ │─────INVITE B────────►│
│ │◄────100 Trying───────│
│◄────100 Trying─────│ │
│ │◄────180 Ringing──────│
│◄────180 Ringing────│ │
│ │◄────200 OK───────────│
│◄────200 OK─────────│ │
│─────ACK A─────────►│ │
│ │─────ACK B───────────►│
│ │ │
│◄═══════════════════╪══════════════════════╡
│ RTP Media (via RTPEngine) │
│◄═══════════════════╪══════════════════════╡
2. Mid-Dialog (Re-INVITE for Hold):
SIP Phone OpenSIPS B2BUA Asterisk
│ │ │
│────INVITE (hold)──►│ │
│ │────INVITE (hold)────►│
│ │◄────200 OK───────────│
│◄────200 OK─────────│ │
│─────ACK───────────►│ │
│ │─────ACK─────────────►│
3. Call Transfer (REFER):
Phone A OpenSIPS B2BUA Phone B Phone C
│ │ │ │
│──────REFER────────►│ │ │
│◄────202 Accepted───│ │ │
│ │───BYE─────────────►│ │
│ │◄──200 OK───────────│ │
│ │ │
│ │─────────INVITE─────────────────────►│
│ │◄────────200 OK──────────────────────│
│◄────BYE────────────│ │
│─────200 OK────────►│ │
Component Details
OpenSIPS Modules
Core Modules
| Module | Purpose |
|---|---|
signaling.so | Basic SIP signaling |
sl.so | Stateless SIP replies |
tm.so | Transaction management |
rr.so | Record-Route handling |
maxfwd.so | Max-Forwards header processing |
sipmsgops.so | SIP message operations |
B2BUA Modules
| Module | Purpose |
|---|---|
b2b_entities.so | Manages B2B entities (server/client UA) |
b2b_logic.so | Implements B2B scenarios and bridging logic |
Supporting Modules
| Module | Purpose |
|---|---|
usrloc.so | User location database |
registrar.so | SIP registration handling |
nathelper.so | NAT traversal support |
rtpengine.so | Media relay integration |
dialog.so | Dialog state tracking |
dispatcher.so | Load balancing |
db_mysql.so | MySQL database backend |
Asterisk Components
| Component | Purpose |
|---|---|
| PJSIP | SIP channel driver |
| MixMonitor | Call recording |
| Echo | Echo test application |
| Queue | Call queuing |
| VoiceMail | Voicemail system |
| ConfBridge | Conference bridging |
RTPEngine
- Purpose: Media relay and manipulation
- Features:
- NAT traversal for RTP
- Codec transcoding
- DTMF handling
- SRTP/RTP conversion
Configuration Reference
Global Parameters
# Feature flags - enable/disable functionality
#!define WITH_NAT # Enable NAT handling
#!define WITH_MYSQL # Enable MySQL persistence
#!define WITH_ASTERISK # Enable Asterisk integration
#!define WITH_RTPENGINE # Enable RTPEngine media relay
# Logging
debug_mode=no # Disable debug mode in production
log_level=3 # 1=Alert, 2=Critical, 3=Error, 4=Warning, 5=Notice, 6=Info, 7=Debug
xlog_level=3 # xlog() function log level
# Process settings
udp_workers=4 # Number of UDP worker processes
tcp_workers=4 # Number of TCP worker processes
auto_aliases=no # Don't auto-create aliases from DNS
# Network bindings
socket=udp:0.0.0.0:5060 # Listen on all interfaces, UDP port 5060
socket=tcp:0.0.0.0:5060 # Listen on all interfaces, TCP port 5060
Transaction Module (tm)
# Timer settings
modparam("tm", "fr_timeout", 5) # Final response timeout (seconds)
modparam("tm", "fr_inv_timeout", 120) # INVITE final response timeout
modparam("tm", "restart_fr_on_each_reply", 1) # Restart timer on provisional replies
modparam("tm", "onreply_avp_mode", 1) # AVP handling in reply routes
# Critical for B2BUA
modparam("tm", "pass_provisional_replies", 1) # Pass 1xx replies to B2B logic
B2B Entities Module
# Hash table configuration
modparam("b2b_entities", "server_hsize", 9) # Server entities hash size (2^9 = 512)
modparam("b2b_entities", "client_hsize", 9) # Client entities hash size (2^9 = 512)
modparam("b2b_entities", "b2b_key_prefix", "B2B") # Prefix for B2B keys
# Database persistence
modparam("b2b_entities", "db_url", "mysql://opensips:opensipspass@127.0.0.1/opensips")
modparam("b2b_entities", "db_mode", 2) # 0=no DB, 1=write-through, 2=write-back
modparam("b2b_entities", "db_table", "b2b_entities")
B2B Logic Module
# Hash table and timers
modparam("b2b_logic", "hash_size", 9) # Logic hash size (2^9 = 512)
modparam("b2b_logic", "cleanup_period", 100) # Cleanup interval (seconds)
modparam("b2b_logic", "max_duration", 43200) # Max call duration (12 hours)
# Callback routes for B2B events
modparam("b2b_logic", "script_req_route", "b2b_logic_request") # Request handler
modparam("b2b_logic", "script_reply_route", "b2b_logic_reply") # Reply handler
# Database persistence
modparam("b2b_logic", "db_url", "mysql://opensips:opensipspass@127.0.0.1/opensips")
modparam("b2b_logic", "db_mode", 2)
modparam("b2b_logic", "db_table", "b2b_logic")
RTPEngine Module
# RTPEngine connection
modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:2223")
# RTPEngine flags used in calls:
# - RTP/AVP: Use RTP Audio/Video Profile
# - replace-origin: Replace SDP origin
# - replace-session-connection: Replace connection info
NAT Helper Module
modparam("nathelper", "natping_interval", 30) # NAT keepalive interval
modparam("nathelper", "ping_nated_only", 1) # Only ping NATted contacts
modparam("nathelper", "sipping_bflag", "SIP_PING") # Branch flag for SIP pings
modparam("nathelper", "sipping_from", "sip:pinger@localhost")
User Location Module
modparam("usrloc", "db_mode", 2) # Write-back mode
modparam("usrloc", "db_url", "mysql://...")
modparam("usrloc", "nat_bflag", "NAT") # Branch flag for NAT
modparam("usrloc", "use_domain", 1) # Include domain in lookups
modparam("usrloc", "timer_interval", 60) # DB sync interval
Routing Logic
Main Route
route {
# 1. Sanity Checks
if (!mf_process_maxfwd_header(10)) {
sl_send_reply(483, "Too Many Hops");
exit;
}
if ($ml > 65535) {
sl_send_reply(513, "Message Too Large");
exit;
}
# 2. CANCEL handling - must be processed by transaction layer
if (is_method("CANCEL")) {
if (t_check_trans()) {
route(RELAY);
}
exit;
}
# 3. Absorb retransmissions
t_check_trans();
# 4. NAT detection
route(NAT_DETECT);
# 5. OPTIONS handling (keepalive)
if (is_method("OPTIONS") && is_myself("$rd")) {
sl_send_reply(200, "OK");
exit;
}
# 6. REGISTER handling
if (is_method("REGISTER")) {
route(REGISTER);
exit;
}
# 7. INVITE handling via B2BUA
if (is_method("INVITE")) {
route(INVITE_HANDLER);
exit;
}
# 8. Other requests
route(LOCATION);
}
INVITE Handler (B2BUA Initialization)
route[INVITE_HANDLER] {
# Handle attended transfer (INVITE with Replaces header)
if ($hdr(Replaces) != null) {
route(HANDLE_REPLACES);
exit;
}
# Store NAT flag for per-leg handling
if (isflagset("NAT")) {
$avp(caller_nat) = 1;
}
# Determine destination based on request
$avp(b2b_to_asterisk) = 0;
if (is_myself("$rd")) {
# Local domain - check registration
if (!lookup("location")) {
# Not registered - route to Asterisk
$avp(b2b_dest) = "sip:" + $rU + "@127.0.0.1:5080";
$avp(b2b_to_asterisk) = 1;
} else {
# Registered user - use contact from location DB
$avp(b2b_dest) = $du;
}
} else {
# External domain - use dispatcher for load balancing
if (!ds_select_dst(1, 4)) {
$avp(b2b_dest) = "sip:" + $rU + "@127.0.0.1:5080";
} else {
$avp(b2b_dest) = $du;
}
$avp(b2b_to_asterisk) = 1;
}
# RTPEngine offer for caller leg
if (has_body("application/sdp")) {
rtpengine_offer("RTP/AVP replace-origin replace-session-connection");
}
# Set destination - preserve user part in R-URI
$rd = "127.0.0.1";
$rp = 5080;
# Initialize B2BUA session
# "top hiding" = internal topology hiding scenario
if (!b2b_init_request("top hiding")) {
sl_send_reply(500, "B2BUA Init Failed");
exit;
}
xlog("L_INFO", "B2BUA: Bridging $fU -> $rU@$rd:$rp\n");
}
B2B Request Route
route[b2b_logic_request] {
xlog("L_INFO", "B2B_REQUEST: $rm from $si\n");
# REFER handling (blind transfer)
if ($rm == "REFER") {
route(HANDLE_REFER);
exit;
}
# BYE handling
if ($rm == "BYE") {
b2b_pass_request();
exit;
}
# Re-INVITE handling (hold/resume/codec change)
if ($rm == "INVITE") {
if (has_body("application/sdp")) {
rtpengine_offer("RTP/AVP replace-origin replace-session-connection");
}
b2b_pass_request();
exit;
}
# UPDATE handling
if ($rm == "UPDATE") {
if (has_body("application/sdp")) {
rtpengine_offer("RTP/AVP replace-origin replace-session-connection");
}
b2b_pass_request();
exit;
}
# Pass through other requests (INFO, MESSAGE, etc.)
b2b_pass_request();
}
B2B Reply Route
route[b2b_logic_reply] {
xlog("L_DBG", "B2B_REPLY: $rs from $si\n");
# Provisional responses (100-199)
if ($rs >= 100 && $rs < 200) {
if (has_body("application/sdp") && $rs == 183) {
# Early media - process SDP answer
rtpengine_answer("RTP/AVP replace-origin replace-session-connection");
}
b2b_handle_reply();
exit;
}
# Success responses (200-299)
if ($rs >= 200 && $rs < 300) {
if (has_body("application/sdp")) {
rtpengine_answer("RTP/AVP replace-origin replace-session-connection");
}
b2b_handle_reply();
exit;
}
# Failure responses (300+)
if ($rs >= 300) {
# Busy (486) or Decline (600) -> voicemail busy
if ($rs == 486 || $rs == 600) {
$avp(vm_uri) = "sip:" + $tU + "-busy@127.0.0.1:5080";
if (b2b_bridge_retry($avp(vm_uri))) {
exit;
}
}
# Timeout (408) or Unavailable (480) -> voicemail noanswer
if ($rs == 408 || $rs == 480) {
$avp(vm_uri) = "sip:" + $tU + "-noanswer@127.0.0.1:5080";
if (b2b_bridge_retry($avp(vm_uri))) {
exit;
}
}
# Dispatcher failover
if ($avp(b2b_to_asterisk) == 1 && ds_next_dst()) {
if (b2b_bridge_retry($du)) {
exit;
}
}
b2b_handle_reply();
}
}
Transfer Handlers
# Blind Transfer (REFER)
route[HANDLE_REFER] {
xlog("L_INFO", "HANDLE_REFER: Transfer from $fU\n");
# Extract Refer-To URI
$var(refer_to) = $hdr(Refer-To);
# Accept the transfer
b2b_send_reply(202, "Accepted");
# Bridge remaining party to new destination
if (!b2b_bridge("peer", $var(refer_to), "")) {
xlog("L_ERR", "HANDLE_REFER: Bridge failed\n");
}
# Terminate transferor's leg
b2b_end_dlg_leg();
}
# Attended Transfer (INVITE with Replaces)
route[HANDLE_REPLACES] {
xlog("L_INFO", "HANDLE_REPLACES: Attended transfer\n");
# Parse Replaces header components
$var(replace_callid) = $(hdr(Replaces){uri.param,call-id});
$var(replace_totag) = $(hdr(Replaces){uri.param,to-tag});
$var(replace_fromtag) = $(hdr(Replaces){uri.param,from-tag});
# Build B2B key
$var(b2b_key) = $var(replace_callid) + ";" +
$var(replace_fromtag) + ";" +
$var(replace_totag);
# Bridge to existing call leg
if (!b2b_bridge_request($var(b2b_key), 1)) {
sl_send_reply(481, "Call Does Not Exist");
exit;
}
}
Database Schema
b2b_entities Table
Stores B2B entity (UA) information for call leg persistence.
CREATE TABLE b2b_entities (
id INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL,
type INT(2) NOT NULL, -- 0=server, 1=client
state INT(2) NOT NULL, -- Entity state
ruri VARCHAR(255), -- Request URI
from_uri VARCHAR(255) NOT NULL, -- From header URI
from_dname VARCHAR(64), -- From display name
to_uri VARCHAR(255) NOT NULL, -- To header URI
to_dname VARCHAR(64), -- To display name
route0 TEXT, -- Route set (direction 0)
route1 TEXT, -- Route set (direction 1)
sockinfo_srv VARCHAR(64), -- Server socket info
param VARCHAR(255), -- Custom parameters
mod_name VARCHAR(32) NOT NULL, -- Module name (b2b_logic)
storage BLOB, -- Binary storage
tag0 VARCHAR(64) NOT NULL, -- Local tag
tag1 VARCHAR(64), -- Remote tag
callid VARCHAR(255) NOT NULL, -- Call-ID
cseq0 INT(11) NOT NULL, -- CSeq (direction 0)
cseq1 INT(11), -- CSeq (direction 1)
lm INT(11) NOT NULL, -- Last modified
lrc INT(11), -- Last reply code
lic INT(11), -- Last INVITE cseq
contact0 VARCHAR(255) NOT NULL, -- Local contact
contact1 VARCHAR(255), -- Remote contact
leg_tag VARCHAR(64), -- Leg tag
leg_cseq INT(11), -- Leg CSeq
leg_contact VARCHAR(255), -- Leg contact
leg_route TEXT -- Leg route
) ENGINE=InnoDB;
-- Version tracking
INSERT INTO version (table_name, table_version) VALUES ('b2b_entities', 2);
b2b_logic Table
Stores B2B session logic and bridging information.
CREATE TABLE b2b_logic (
id INT(10) UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL,
si_key VARCHAR(64) NOT NULL, -- Session identifier key
scenario VARCHAR(64), -- Scenario name
sstate INT(2) NOT NULL DEFAULT 0, -- Session state
lifetime INT(10) NOT NULL DEFAULT 0, -- Session lifetime
-- Entity 1 (typically server/caller side)
e1_type INT(2) NOT NULL, -- Entity type
e1_sid VARCHAR(64), -- Entity session ID
e1_to VARCHAR(255) NOT NULL, -- To URI
e1_from VARCHAR(255) NOT NULL, -- From URI
e1_key VARCHAR(64) NOT NULL, -- Entity key
e1_sdp TEXT, -- SDP body
-- Entity 2 (typically client/callee side)
e2_type INT(2) NOT NULL,
e2_sid VARCHAR(64),
e2_to VARCHAR(255) NOT NULL,
e2_from VARCHAR(255) NOT NULL,
e2_key VARCHAR(64) NOT NULL,
e2_sdp TEXT,
-- Entity 3 (for 3-way calls/transfers)
e3_type INT(2),
e3_sid VARCHAR(64),
e3_to VARCHAR(255),
e3_from VARCHAR(255),
e3_key VARCHAR(64),
CONSTRAINT si_key_idx UNIQUE (si_key)
) ENGINE=InnoDB;
-- Version tracking
INSERT INTO version (table_name, table_version) VALUES ('b2b_logic', 5);
Supporting Tables
-- Location table (user registrations)
CREATE TABLE location (
contact_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(64) NOT NULL,
domain VARCHAR(64) NOT NULL,
contact VARCHAR(512) NOT NULL,
received VARCHAR(255),
path VARCHAR(255),
expires INT UNSIGNED NOT NULL,
q DECIMAL(10,2) DEFAULT 1.0,
callid VARCHAR(255) NOT NULL,
cseq INT NOT NULL,
last_modified DATETIME NOT NULL,
flags INT UNSIGNED DEFAULT 0,
user_agent VARCHAR(255),
socket VARCHAR(64),
methods INT UNSIGNED,
INDEX username_idx (username, domain)
) ENGINE=InnoDB;
-- Dispatcher table (load balancing destinations)
CREATE TABLE dispatcher (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
setid INT NOT NULL DEFAULT 0,
destination VARCHAR(192) NOT NULL,
socket VARCHAR(128),
state INT DEFAULT 0,
probe_mode INT DEFAULT 0,
weight VARCHAR(64) DEFAULT '1',
priority INT DEFAULT 0,
attrs VARCHAR(128),
description VARCHAR(64)
) ENGINE=InnoDB;
Call Flow
Basic Call Setup
1. SIP Phone sends INVITE to OpenSIPS :5060
2. OpenSIPS receives INVITE in main route
3. NAT detection and fix applied
4. INVITE routed to INVITE_HANDLER
5. B2BUA initialized with b2b_init_request("top hiding")
6. OpenSIPS creates:
- Server entity (for incoming leg)
- Client entity (for outgoing leg)
7. New INVITE sent to Asterisk :5080
8. Asterisk answers with 200 OK
9. B2BUA correlates responses
10. 200 OK sent back to SIP Phone
11. ACK exchanges complete call setup
12. RTP flows through RTPEngine
Call Transfer Flow
Blind Transfer:
1. Phone A in call with Phone B (via B2BUA)
2. Phone A sends REFER to transfer to Phone C
3. B2BUA receives REFER in b2b_logic_request
4. HANDLE_REFER route processes transfer
5. B2BUA sends 202 Accepted to Phone A
6. B2BUA bridges Phone B to Phone C
7. B2BUA terminates leg with Phone A
8. Phone B now connected to Phone C
Attended Transfer:
1. Phone A in call with Phone B
2. Phone A places call on hold
3. Phone A calls Phone C (consultation)
4. Phone A sends INVITE with Replaces header
5. B2BUA processes in HANDLE_REPLACES
6. B2BUA bridges Phone B with Phone C
7. Phone A disconnected from both calls
Deployment Guide
Prerequisites
- Docker and Docker Compose
- Network access to SIP ports (5060, 5080)
- RTP port range (10000-20000)
Directory Structure
b2bua-cc-sys/
├── docker-compose.yml # Main stack definition
├── docker-compose.test.yml # Test stack (SIPp)
├── .env # Environment configuration
├── README.md # Project overview
├── TESTING.md # Testing guide
├── docs/
│ ├── B2BUA-ARCHITECTURE.md # This file
│ └── detail-flow.md # Detailed call flows
├── opensips/
│ ├── Dockerfile
│ ├── opensips.cfg # Main OpenSIPS config
│ ├── opensips-extra.cfg # Extended config
│ └── docker-entrypoint.sh
├── asterisk/
│ ├── Dockerfile
│ ├── docker-entrypoint.sh
│ ├── recordings/ # Call recordings
│ └── conf/
│ ├── pjsip.conf
│ ├── extensions.conf
│ ├── queues.conf
│ ├── voicemail.conf
│ ├── confbridge.conf
│ └── ...
├── rtpengine/
│ ├── Dockerfile
│ ├── rtpengine.conf
│ └── docker-entrypoint.sh
├── mysql/
│ └── init/
│ └── 01-opensips-init.sql
├── scripts/
│ ├── setup.sh
│ ├── user-manage.sh
│ └── test.sh
└── sipp-test/
├── Dockerfile
├── README.md
├── run_tests.sh
├── generate_pcap.py
├── scenarios/ # SIPp test scenarios
└── pcap/ # RTP audio files
Deployment Steps
# 1. Clone/download configuration
cd /path/to/b2bua-cc-sys
# 2. Start the stack
docker compose up -d
# 3. Verify services are running
docker compose ps
# 4. Check OpenSIPS logs
docker logs voip-opensips
# 5. Verify B2B modules loaded
docker exec voip-opensips opensips-cli -x mi which | grep b2b
# 6. Check database tables
docker exec voip-mysql mysql -uopensips -popensipspass opensips \
-e "SHOW TABLES LIKE 'b2b%'"
Environment Variables
| Variable | Default | Description |
|---|---|---|
SIP_DOMAIN | sip.example.com | SIP domain |
PUBLIC_IP | auto | Public IP address |
MYSQL_HOST | 127.0.0.1 | MySQL host |
MYSQL_USER | opensips | MySQL username |
MYSQL_PASSWORD | opensipspass | MySQL password |
MYSQL_DATABASE | opensips | Database name |
ASTERISK_IP | 127.0.0.1 | Asterisk IP |
ASTERISK_PORT | 5080 | Asterisk SIP port |
RTPENGINE_SOCK | udp:127.0.0.1:2223 | RTPEngine socket |
Testing Guide
Using SIPp
# Basic call test (UAC scenario)
docker exec voip-sipp sipp -sn uac \
-s 600 \ # Extension to call
-d 5000 \ # Call duration (ms)
-l 1 \ # Concurrent calls limit
-m 1 \ # Total calls
-r 1 \ # Call rate
127.0.0.1:5060 # OpenSIPS address
# Expected output: 200 OK received, call completes
UAC to UAS Testing with Voice Recording
The system includes dedicated SIPp scenarios for testing UAC (caller) to UAS (callee) communication with voice recording verification.
Quick Start: UAC to UAS Tests
# Start the SIPp test container
docker compose -f docker-compose.test.yml up -d --build
# Test 1: Direct UAC -> UAS (no recording, tests SIP signaling)
docker exec -it voip-sipp /sipp/run_tests.sh uac-uas
# Test 2: UAC -> UAS via Proxy (with Asterisk recording)
docker exec -it voip-sipp /sipp/run_tests.sh uac-uas-proxy -e 600
Test Architecture
Test Mode 1: Direct (No Recording)
┌─────────┐ ┌─────────┐
│ UAC │ ─────── SIP/RTP ──────────── │ UAS │
│ :5090 │ │ :5062 │
└─────────┘ └─────────┘
Test Mode 2: Via Proxy (With Recording)
┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐
│ UAC │ ──► │ OpenSIPS │ ──► │ Asterisk │ ──► │Extension│
│ :5090 │ │ :5060 │ │ :5080 │ │ (600) │
└─────────┘ └──────────┘ └──────────┘ └─────────┘
│
▼
┌──────────────┐
│ Recording │
│ (WAV file) │
└──────────────┘
Available Test Scenarios
| Scenario | File | Description |
|---|---|---|
uac_caller.xml | UAC | Outbound call initiator with PCAP audio |
uas_receiver.xml | UAS | Inbound call receiver with PCAP audio |
uac_to_uas_via_proxy.xml | UAC via B2BUA | Call through OpenSIPS (enables recording) |
Verifying Recordings
After running tests through the proxy:
# List recordings
docker exec voip-asterisk ls -la /var/spool/asterisk/recordings/
# Recording format: YYYYMMDD-HHMMSS_<caller>_to_<extension>.wav
# Example: 20260120-143025_test-uac_to_600.wav
# Copy to host
docker cp voip-asterisk:/var/spool/asterisk/recordings/20260120-143025_test-uac_to_600.wav ./
# Verify file
file ./20260120-143025_test-uac_to_600.wav
See sipp-test/README.md for complete testing documentation.
Verification Commands
# Check OpenSIPS is running
docker exec voip-opensips ps aux | grep opensips
# Check active B2B sessions
docker exec voip-opensips opensips-cli -x mi b2b_list
# Check Asterisk channels
docker exec voip-asterisk asterisk -rx "core show channels"
# Check call recordings
docker exec voip-asterisk ls -la /var/spool/asterisk/recordings/
# Check OpenSIPS logs for B2BUA activity
docker logs voip-opensips 2>&1 | grep B2BUA
# Check RTPEngine status
docker exec voip-rtpengine rtpengine-ctl list sessions
Test Scenarios
| Test | Command | Expected Result |
|---|---|---|
| Echo test | Call 600 | Hear echo of your voice |
| Music on hold | Call 601 | Hear music |
| Speaking clock | Call 602 | Hear current time |
| Voicemail | Call *98 | Access voicemail |
| UAC->UAS direct | run_tests.sh uac-uas | SIP call completes |
| UAC->UAS proxy | run_tests.sh uac-uas-proxy | Call + recording created |
Troubleshooting
Common Issues
1. OpenSIPS fails to start - B2B module errors
Symptoms:
ERROR: error during table version check
ERROR: Failed to restore data from database
Solution:
# Check table exists and version is correct
docker exec voip-mysql mysql -uopensips -popensipspass opensips \
-e "SELECT * FROM version WHERE table_name LIKE 'b2b%'"
# Should show:
# b2b_entities | 2
# b2b_logic | 5
# If incorrect, recreate tables with correct schema
2. Calls fail with “484 Address Incomplete”
Symptoms:
- INVITE reaches Asterisk but user part is missing
- To header shows
<sip:127.0.0.1>instead of<sip:600@127.0.0.1>
Solution:
- Set
$rdand$rpseparately instead of full$ru - Ensure user part (
$rU) is preserved in routing
3. No audio / one-way audio
Symptoms:
- Call connects but no audio
- Audio in one direction only
Solution:
# Check RTPEngine is running
docker exec voip-rtpengine rtpengine-ctl list sessions
# Verify RTPEngine socket in config
grep rtpengine_sock /etc/opensips/opensips.cfg
# Check NAT detection is working
docker logs voip-opensips 2>&1 | grep NAT
4. Recordings not created
Symptoms:
- Calls work but no .wav files created
Solution:
# Check recording directory permissions
docker exec voip-asterisk ls -la /var/spool/asterisk/recordings/
# Check MixMonitor in dialplan
docker logs voip-asterisk 2>&1 | grep MixMonitor
# Verify RECORD_CALLS=yes in extensions.conf
Debug Mode
# Enable OpenSIPS debug logging
# In opensips.cfg:
debug_mode=yes
log_level=7
xlog_level=7
# Enable Asterisk PJSIP logging
docker exec voip-asterisk asterisk -rx "pjsip set logger on"
# View detailed SIP traces
docker logs -f voip-asterisk 2>&1 | grep -E "SIP|INVITE|200"
Log Locations
| Service | Log Location |
|---|---|
| OpenSIPS | docker logs voip-opensips |
| Asterisk | docker logs voip-asterisk |
| RTPEngine | docker logs voip-rtpengine |
| MySQL | docker logs voip-mysql |
References
- OpenSIPS B2B_LOGIC Module
- OpenSIPS B2B_ENTITIES Module
- OpenSIPS B2BUA Tutorial
- Asterisk PJSIP Configuration
- RTPEngine Documentation
Document Version: 1.0 Last Updated: January 2026 OpenSIPS Version: 3.4.x