Kamailio: Discovery Tutorial – Part 1

I discovered Kamailio a year ago when I needed to develop an SBC (Session Border Controller) whose primary function was to protect an Asterisk instance. Since then, the initial requirement has evolved, and by definition, the technical solution has as well. I have been able to improve my skills with the software and, in the process, gained flexibility with it within a VoIP technical stack.

Since online resources regarding Kamailio use cases are relatively limited, I will try to explain this software through a series of articles featuring configuration examples.

First and foremost, before diving deeper into this tutorial, it is recommended to have some knowledge of SIP (RFC 3261).

Definitions

To better understand the concepts mentioned in this article and the broader Kamailio ecosystem, here is a list of essential definitions.

Core SIP Roles

  • SIP (Session Initiation Protocol): An application-layer signaling protocol used for initiating, maintaining, and terminating real-time sessions that include voice, video, and messaging.
  • UAC (User Agent Client): A logical entity that creates a new SIP request. For example, when you pick up a VoIP phone to dial a number, the phone acts as a UAC to initiate the INVITE.
  • UAS (User Agent Server): A logical entity that generates a response to a SIP request. When a phone receives an INVITE and sends back a 180 Ringing or 200 OK, it is acting as a UAS .
  • B2BUA (Back-to-Back User Agent): Unlike a proxy, a B2BUA acts as both a UAS and a UAC simultaneously. it terminates the incoming call leg and starts a new outgoing leg, giving it full control over the media and signaling (e.g., Asterisk or FreeSWITCH).

Note: Most SIP endpoints (like IP phones or PBXs) act as both a UAC and UAS depending on whether they are making or receiving a call

VoIP Components

  • SIP Proxy: A server that acts as an intermediary, forwarding SIP requests to another entity. Kamailio’s primary strength is acting as a high-performance, programmable proxy.
  • Registrar: A server that accepts REGISTER requests and places the information it receives (the association between a user’s URI and their current IP address) into a location service.
  • SBC (Session Border Controller): A specialized device or software (like Kamailio) used to protect and manage the flow of signaling and media at the “border” of a network. It handles security (hiding internal topology), NAT traversal, and protocol normalization.
  • AoR (Address of Record): A SIP URI that points to a domain with a location service where the user can be reached. For example: sip:user@example.com.
  • RTPEngine: A proxy for RTP traffic. Since Kamailio generally only handles signaling (SIP), it often uses rtpengine to proxy the actual audio/video data (RTP) between different network interfaces and users.

Switching Classes

  • Class 4 Softswitch (Wholesale/Carrier): These are designed for routing large volumes of long-distance VoIP traffic between different providers and networks. They focus on Least Cost Routing (LCR), scale, and reliability rather than user features like voicemail or call waiting.
  • Class 5 Softswitch (Retail/Resident): These are intended for the end-user. They provide the actual telephony services and “features” we are familiar with, such as call forwarding, automated attendants, Hunt Groups, and Voicemail-to-Email.

What is Kamailio?

Kamailio (formerly OpenSER) is a free and open-source SIP server. It is written in C with a specific focus on performance. Kamailio is highly configurable and can act as a SIP registrar, proxy, or even a load-balancer, thanks to its modular architecture.

Contrary to what one might think, Kamailio is not a PBX and differs from Asterisk, FreeSwitch, and the like. Kamailio is also not a B2BUA (Back-to-Back User Agent); it only manages signaling (SIP), whereas a PBX will additionally manage media (voice) or include call-center features.

To meet a specific need with Kamailio, you must configure it. The default Kamailio configuration does nothing; however, it provides a glimpse of the software’s possibilities. This is also why SIP knowledge is necessary, as you must have an idea of the requests and responses required for the software to function correctly.

Note : media is not handled by Kamailio. For this, I recommand RTPEngine from SIPWise.

Where to find Kamailio documentation?

On the official Kamailio website, you can find two types of documentation: the official wiki and the modules documentation. The official documentation is very thorough, and both the Wiki and the module pages explain Kamailio’s configuration very well.

On the other hand, when it comes to specific use cases and architectures involving third-party components, it is harder to get a high-level overview. Figures like Fred POSNER often praise Kamailio, and thankfully, the Kamailio World YouTube channel exists. Additionally, it is possible to draw inspiration from OpenSIPS documentation and its tutorials, although differences between the two projects appear very quickly.

Installing Kamailio

Kamailio is available on Linux and BSD. On Ubuntu, install Kamailio from the official repositories: $ sudo apt install kamailio

Edit the configuration: /etc/kamailio.cfg.

It is also possible to use Kamailio with Docker using a configuration like the one below:

networks:
  default:
  # database:
  #   external: true

services:
  # Primary Kamailio instance
  kamailio:
    image: ghcr.io/kamailio/kamailio:6.0.4-bookworm
    restart: unless-stopped
    volumes:
      - ./kamailio:/etc/kamailio
      - ./kamailio/logs/kamailio:/var/log/kamailio
    network_mode: host # Expose 5060 for TCP/UDP and 5061 for TLS

  # RTP Engine service
  rtpengine:
    image: fonoster/rtpengine:latest
    container_name: rtpengine
    restart: unless-stopped
    network_mode: "host" # At the moment this option only works in Linux
    environment:
      # RTPENGINE_PUBLIC_IP: ${DOCKER_HOST_ADDRESS:-192.168.1.210} When commented, use Netdiscover
      RTPENGINE_BIND_NG_IP: ${RTPENGINE_BIND_NG_IP:-127.0.0.1}
      RTPENGINE_BIND_NG_PORT: ${RTPENGINE_BIND_NG_PORT:-2223}
      RTPENGINE_PORT_MIN: 10000
      RTPENGINE_PORT_MAX: 20000
      RTPENGINE_LOG_LEVEL: "6"

Note that network_mode: host is only supported on Linux. Furthermore, SIP and RTP protocols do not handle NAT (Network Address Translation) very well if you decide to expose Kamailio and RTPEngine ports via Docker mapping.

Some SIP Notions

For this first article, I will list some of the SIP requests you should know. They will be useful whether you are working with Kamailio or any other PBX.

SIP RequestDescription
INVITEInitiates a call session.
ACKConfirms the receipt of a final response (e.g., after INVITE).
BYETerminates an existing session.
CANCELCancels a pending INVITE request before it is answered.
REGISTERRegisters a user’s location with a SIP server.
OPTIONSQueries the capabilities of a server or endpoint.
INFOCarries mid-session control information (e.g., DTMF).
PRACKAcknowledges reliable provisional responses.
UPDATEModifies session parameters without re-issuing an INVITE.
REFERRequests the recipient to initiate a new request (often for call transfers).

Discovering Kamailio

Let’s begin this tutorial with Kamailio. Here are a few SIP concepts that will be useful if you are a beginner. At the very least, they will serve as a refresher.

Example of an INVITE request

Take the example of a SIP request used to place a call: INVITE. Imagine two users, 1001 and 1002, where 1001 wants to call 1002.

According to RFC 3261, 1001 must send a SIP request to 1002; however, this implies knowing the IP address and the port where 1002 is available. In practice, it never works this way; 1001 will send this request to a SIP server which will be responsible for relaying it (like a proxy) or issuing a new one (like a PBX/B2BUA).

INVITE sip:1002@voip.operator.re SIP/2.0

In a setup with two phones, this request makes 1002‘s phone “ring.” If we sent this message to a generic PBX, it would have the necessary logic to know it has an extension 1001 and would ring extension 1002.

With a PBX

A generic PBX examines the Request-URI in the received INVITE message and has predefined logic to know that 1002 is a device it has registered and that we want to connect to that device. It then sends the call to the corresponding device (1002). This is the “classic” operation of Asterisk, FreeSwitch, or any PBX. To put it simply, if we send this request to Asterisk, it would verify that it has the users (endpoints in PJSIP) and, if the dialplan allows, redirect the request to the recipient (example dialplan line: Dial(PJSIP/${EXTEN},30,tTr)). Response management is automatic with a PBX, as it focuses on the core logic: establishing calls between users, whether local or remote via a trunk.

With Kamailio

Kamailio works in a completely different way. If you want Kamailio to react to this request, you must write it into its configuration and instruct it—much like a programming language—on what actions to take. Roughly speaking, this would involve programming Kamailio to accept INVITE requests, check if the recipient’s Address of Record (AoR) is in memory, respond with a 100 Trying, forward the INVITE to the destination, and finally return a 200 OK to the caller. This results in the following diagram:

Call flow using Kamailio

Here, I have omitted the fact that we must also handle 4XX responses if the callee is busy or away, manage users, and handle voice media (RTP). While all of this might seem difficult to manage, it is essentially “programming” in the broad sense of the term.

Even if this example seems basic, it helps you understand how the system handles and routes your SIP messages. Ultimately, this opens the door to many features, such as managing prepaid or postpaid call authorizations, saving Call Detail Records (CDRs), implementing firewalling, load-balancing, Least Cost Routing (LCR), and more.

Final Word

Kamailio is a software that is difficult to master. Initial encounters can be daunting, and you may sometimes feel like giving up. With a bit of experience, it is possible to achieve great things with Kamailio, which can be used as a Class 5 softswitch (for profesionnals) or a Class 4 softswitch (for ISP and ITSP).

How to interconnect Asterisk servers using PJSIP

In the world of VoIP communication, interconnecting Asterisk servers is a game-changer for small ISPs, businesses, or remote offices looking to build scalable phone systems. As someone who works daily with Asterisk for VoIP set-ups, I’ve seen how this open-source PBX can transform a simple server into a powerful trunk provider – and also a money maker. Whether you’re providing SIP trunks to customers or would like to link branch offices seamlessly, this guide walks you through using Asterisk PJSIP configuration and dial plan logic to create reliable interconnections. We’ll cover everything from basics to hands-on set-up, optimized for VoIP trunking and SIP endpoint management.

If you’re searching for “how to set up Asterisk trunks” or “PJSIP interconnection tutorial,” you’ve come to the right place. Let’s dive in and make your Asterisk server interconnection straightforward and efficient.

What is Asterisk?

Asterisk is the world’s leading open-source software for building communications applications, often called the Swiss Army knife of telephony as it comes with really a lot of features. From simple telephony soft switch to a complex media handling in a SBC, Asterisk is just amazing. Developed by Digium (now part of Sangoma), it powers companies from simple PBXs to complex call centers. Nevertheless Asterisk handles voice calls, video, and messaging over IP networks, making it ideal for VoIP server setups.

Why choose Asterisk for interconnecting servers? It’s free, highly customizable, and supports modern protocols like PJSIP — a lightweight, standards-compliant SIP stack that’s replaced the older chan_sip module. PJSIP offers better performance, NAT traversal, and security features, perfect for SIP trunk provisioning in distributed environments. In a small ISP scenario, like the one I work in, Asterisk lets you act as a central hub, routing calls between customer endpoints and external providers without breaking a sweat.

For those new to telephony, think of Asterisk as the brain behind your phone system: it processes incoming calls, applies rules via dial plans, and bridges connections across networks.

Understanding Trunks

In telephony, a trunk refers to a communication pathway capable of handling multiple simultaneous calls – like an highway for phone calls. Historically, trunks were physical lines like E1 in Europe or T1 in the US, managed by telecom giants such as France Telecom (now Orange). These could carry dozens of channels, forming the core of traditional PSTN networks. Of course, with time, things thankfully changed.

With the rise of the internet, VoIP revolutionized this. Trunks evolved into virtual connections using IP protocols, allowing unlimited scalability at a fraction of the cost. In Asterisk trunk configuration, a trunk is essentially a logical link between your server and another endpoint, like a remote office or customer PBX. This set-up enables seamless Asterisk interconnections, where calls flow bidirectionally without quality loss.

Key benefits for VoIP trunking:

  • Cost Savings: No need for expensive hardware; just IP bandwidth.
  • Scalability: Handle 10 or 10,000 calls by adding server resources. Perfect for call centers.
  • Flexibility: Route calls based on rules, like time of day or caller ID.

If you’re optimizing for “Asterisk SIP trunk set-up,” trunks are your starting point for reliable, multi-site VoIP deployments.

What is SIP? The Protocol Powering Modern VoIP

SIP, or Session Initiation Protocol, is the standard (RFC 3261) for initiating, maintaining, and terminating real-time sessions in VoIP. Pronounced “sip,” it’s a protocol that sets up a phone call over the internet. SIP handles signalling—think invitations, acceptances, and goodbyes—while leaving the actual voice data to other protocols.

In PJSIP for Asterisk, SIP is implemented efficiently for server-to-server interconnections. For example, when two Asterisk servers connect via a trunk, SIP messages negotiate the call parameters, such as codecs and endpoints. Common SIP methods include INVITE (to start a call), ACK (to confirm), and BYE (to end it).

What is Media? Handling the Audio in Your Calls

It’s not easy in the first time to understand what is “Media” in VoIP. As explained before, SIP is a signalling protocol and does not handle media (voice, video, etc.). Asterisk can seamlessly handle media with SIP. While SIP manages the “who” and “when” of a call, media is the actual content—the voice, video, or data streams. In VoIP, media travels via RTP (Real-time Transport Protocol) over UDP, separate from SIP signalling for efficiency. Codecs like G.711 (high-quality, bandwidth-heavy) or Opus (adaptive, low-latency) compress and transmit this data.

In Asterisk media handling, you configure trunks to bridge media streams securely, often using RTPEngine for NAT traversal in interconnected setups. Without proper media routing, calls might suffer from one-way audio or jitter—common pitfalls in multi-server VoIP architectures.

Pro tip: For optimal Asterisk PJSIP media setup, enable direct media (re-INVITE) between endpoints to bypass the server, saving CPU and bandwidth in large-scale trunks.

Step-by-Step: Configure a Trunk in Asterisk for Interconnections

Now, let’s get practical. We’ll configure two Asterisk servers: Server A (your main ISP hub, acting as the remote endpoint) and Server B (a customer or branch office PBX). This PJSIP trunk tutorial assumes Asterisk 16+ with PJSIP enabled (check via module show like pjsip in CLI).

Prerequisites for Successful Setup

  • Both servers on stable networks with public IPs or STUN/TURN for NAT.
  • Firewall ports open: 5060 UDP/TCP (SIP), 10000-20000 UDP (RTP media).
  • Install PJSIP: dnf install asterisk-pjsip (CentOS) or equivalent.
  • Backup configs: cp /etc/asterisk/pjsip.conf /etc/asterisk/pjsip.conf.bak.

Reload after changes: asterisk -rx "pjsip reload".

Server A: Configuring the Remote Endpoint (ISP Side)

On Server A (e.g., IP: 192.0.2.1), create a trunk to accept connections from Server B. Edit /etc/asterisk/pjsip.conf:

[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060

[trunk-customer-b]
type=endpoint
transport=transport-udp
context=from-trunk  ; Dialplan context for incoming calls
disallow=all
allow=ulaw,alaw,gsm  ; Supported codecs for VoIP quality
auth=trunk-customer-b-auth
aors=trunk-customer-bdirect_media=no  ; Force media through server for recording/security

[trunk-customer-b-auth]
type=auth
auth_type=userpass
username=trunkuser  ; Shared secret with Server B
password=strongpass123
realm=192.0.2.1

[trunk-customer-b-aor]
type=aorcontact=sip:192.0.2.2:5060  ; Server B's IP

This sets up Server A as a secure SIP trunk provider. The auth section enforces username/password for PJSIP authentication in Asterisk, preventing unauthorized access.

Now, add dialplan rules in /etc/asterisk/extensions.conf for routing calls from the trunk:

[from-trunk]
exten => _X.,1,NoOp(Incoming from trunk) 
same => n,Dial(PJSIP/${EXTEN}@local-extension)  ; Route to internal users same => n,Hangup()

This handles incoming calls, forwarding them to local extensions. For outbound to Server B, we’ll cover in the next section.

Server B: Configuring the Customer Endpoint (Branch Office Side)

On Server B (e.g., IP: 192.0.2.2), register to Server A as a trunk client. In /etc/asterisk/pjsip.conf:

[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060
local_net=192.168.0.0/16  ; Your internal network for NAT

[trunk-isp-a]
type=endpoint
transport=transport-udp
context=from-isp  ; Context for calls from Server A
disallow=all
allow=ulaw,alaw,gsm
auth=trunk-isp-a-auth
aors=trunk-isp-a-aor
outbound_auth=trunk-isp-a-auth
from_user=trunkuser
from_domain=192.0.2.1

[trunk-isp-a-auth]
type=auth
auth_type=userpass
username=trunkuser
password=strongpass123realm=192.0.2.1

[trunk-isp-a-aor]
type=aor
contact=sip:192.0.2.1:5060

Here, Server B authenticates to Server A, enabling bidirectional VoIP trunking. For outbound calls to the ISP trunk:

In /etc/asterisk/extensions.conf:

[from-internal]
exten => _9NXXNXXXXXX,1,NoOp(Outbound to ISP) 
same => n,Set(CALLERID(num)=${CALLERID(num)})  ; Preserve caller ID 
same => n,Dial(PJSIP/${EXTEN:1}@trunk-isp-a)  ; Strip 9 prefix, dial via trunk 
same => n,Hangup()

[from-isp] 
exten => _X.,1,NoOp(Incoming from ISP trunk) 
same => n,Dial(Local/${EXTEN}@internal-extensions)  ; Route to local users same => n,Hangup()

Test the setup: From Server B, dial an extension on Server A (e.g., Dial(PJSIP/100@trunk-isp-a)). Use pjsip show endpoints in Asterisk CLI to verify registration.

Troubleshooting Common Issues in Asterisk PJSIP Interconnections

  • One-Way Audio: Enable direct_media=yes or check RTP ports/firewalls.
  • Registration Fails: Verify auth credentials and realms match.
  • Codec Mismatch: Align allow/disallow on both ends.

For advanced Asterisk troubleshooting, enable debug logging: pjsip set logger on.

Best Practices for Secure and Scalable VoIP Trunks

  • Use TLS for SIP (add protocol=tls in transports) to encrypt signalling.
  • Implement ACLs in pjsip.conf to restrict IPs.
  • Monitor with tools like sngrep for SIP traffic analysis.
  • Scale with multiple trunks for load balancing in multi-Asterisk environments.

By following this Asterisk PJSIP interconnection guide, you’ll have a robust VoIP trunk up and running.