Skip to content
LIVE FEED

BlogOpenSIPS B2BUA Architecture Documentation

OpenSIPS B2BUA Architecture Documentation

Table of Contents

  1. Overview
  2. Architecture
  3. Component Details
  4. Configuration Reference
  5. Database Schema
  6. Call Flow
  7. Deployment Guide
  8. Testing Guide
  9. 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?

FeatureSIP ProxyB2BUA
SIP Dialogs1 end-to-end2 independent
Call-IDPreservedDifferent per leg
Mid-dialog routingVia Record-RouteInternal handling
Call transfersPass-throughProcess & re-bridge
Media controlLimitedPer-leg manipulation
Topology hidingLimitedFull hiding
Call recordingComplexNative 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

ModulePurpose
signaling.soBasic SIP signaling
sl.soStateless SIP replies
tm.soTransaction management
rr.soRecord-Route handling
maxfwd.soMax-Forwards header processing
sipmsgops.soSIP message operations

B2BUA Modules

ModulePurpose
b2b_entities.soManages B2B entities (server/client UA)
b2b_logic.soImplements B2B scenarios and bridging logic

Supporting Modules

ModulePurpose
usrloc.soUser location database
registrar.soSIP registration handling
nathelper.soNAT traversal support
rtpengine.soMedia relay integration
dialog.soDialog state tracking
dispatcher.soLoad balancing
db_mysql.soMySQL database backend

Asterisk Components

ComponentPurpose
PJSIPSIP channel driver
MixMonitorCall recording
EchoEcho test application
QueueCall queuing
VoiceMailVoicemail system
ConfBridgeConference 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

VariableDefaultDescription
SIP_DOMAINsip.example.comSIP domain
PUBLIC_IPautoPublic IP address
MYSQL_HOST127.0.0.1MySQL host
MYSQL_USERopensipsMySQL username
MYSQL_PASSWORDopensipspassMySQL password
MYSQL_DATABASEopensipsDatabase name
ASTERISK_IP127.0.0.1Asterisk IP
ASTERISK_PORT5080Asterisk SIP port
RTPENGINE_SOCKudp:127.0.0.1:2223RTPEngine 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

ScenarioFileDescription
uac_caller.xmlUACOutbound call initiator with PCAP audio
uas_receiver.xmlUASInbound call receiver with PCAP audio
uac_to_uas_via_proxy.xmlUAC via B2BUACall 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

TestCommandExpected Result
Echo testCall 600Hear echo of your voice
Music on holdCall 601Hear music
Speaking clockCall 602Hear current time
VoicemailCall *98Access voicemail
UAC->UAS directrun_tests.sh uac-uasSIP call completes
UAC->UAS proxyrun_tests.sh uac-uas-proxyCall + 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 $rd and $rp separately 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

ServiceLog Location
OpenSIPSdocker logs voip-opensips
Asteriskdocker logs voip-asterisk
RTPEnginedocker logs voip-rtpengine
MySQLdocker logs voip-mysql

References


Document Version: 1.0 Last Updated: January 2026 OpenSIPS Version: 3.4.x

Contact
Facebook
TOP