← Documentation

RTPProxy 3.2 Manual

Maksym Sobolyev

Sippy Labs

Donat Zenichev

30 May 2019


Table of Contents

Introduction
What is RTPProxy
Backstory
Capabilities
Principles of Operation
Content of the Package
Installation of RTPProxy
Pre-built RTPProxy
Compilation from sources
Getting Started
Configuration
Command Line Parameters
Configuration File
Startup Script
Logging
Configuration of control socket (using systemd)
Modules
Loading Modules
Controlling Modules
acct_csv
acct_rtcp_hep
catch_dtmf
dtls_gw
ice_lite
Development
Embedding RTPProxy: librtpproxy
Building and Installing
Interface
Controlling an Embedded Instance
Packet Ports
Testing
Running Tests
Writing Tests with RPTL

Introduction

What is RTPProxy

The RTPProxy is a high-performance software proxy for RTP streams that can work together with Sippy B2BUA, Kamailio, OpenSIPS and SER (SIP Express Router).

Note

The latest stable version is RTPProxy 3.2

Backstory

RTPProxy was originally developed by Maxim Sobolyev in 2003 with a purpose of VoIP calls facilitation to/from SIP User Agents located behind NAT or firewalls. That time, there were a few cases where direct end-to-end communication between users behind the NAT was not possible. RTPProxy, in common with a SIP proxy, overcomes those obstacles by acting as an intermediary point for the RTP streams.

Afterwards, the RTPProxy has become widely used by VoIP service providers that are in a demand for optimizing a traffic flow in their networks.

Later on it became apparent that there are many other possible uses of this software. It can be used in common with a signaling element (SIP Proxy or SIP B2BUA) to build complex VoIP networks, to optimize a traffic flow, to collect voice quality information and so on.

RTPProxy is now actively developed and maintained by the Sippy Software, Inc. and is available on GitHub of Sippy Labs - https://github.com/sippy/rtpproxy.

Over the years, many other individuals and organizations have contributed features and fixes into the code base, including Vonage, SipPulse, Voicenter, RateTel, either directly or by sponsoring respective development efforts (often both).

Capabilities

Originally it was designed for handling of NAT scenarios. It can also act as a generic media relay, as well as a gateway for RTP sessions between IPv4 and IPv6 networks.

It can perform a number of additional functions on RTP streams, including call recording, playing pre-recorded announcements, real-time stream copying and RTP payload re-framing.

The RTPProxy supports some advanced features, such as remote control mode, allowing a building of scalable distributed SIP VoIP networks. The rtpproxy modules of OpenSIPS and Kamailio (both descendants of the SIP Express Router, whose nathelper module originally provided this functionality), as well as the Sippy B2BUA, allow using multiple RTPProxy instances running on remote machines for fault-tolerance and load-balancing purposes.

An advanced high-capacity clustering and a load balancing is available through the use of RTP Cluster middleware.

The software also supports MOH/pre-recorded prompts injection, video relaying and RTP session recording to a local file or remote UDP listener(s).

Principles of Operation

  1. Firstly, when a SIP proxy receives an INVITE request, it extracts a call-id from it and hands it to the proxy via the control socket. Then, in its turn, a proxy looks for an existing session with the same id. If the session exists, it returns a UDP port for that session. In case it doesn't exist, it creates a new session and binds it to a free UDP port, randomly selected from the range specified at the start time, and returns the number of that port to a SIP proxy called it. Upon receiving a reply from the proxy, SIP Proxy (Sippy B2BUA, OpenSIPS or Kamailio) in its turn replaces media IP:port in the SDP body, in order to point to the media proxy and then it forwards a request as usually.

  2. When it comes to receiving of non-negative SIP reply with the SDP body, then SIP proxy again extracts call-id from it and hands it to the rtpproxy. In this case the rtpproxy does not allocate a new session, if it doesn't exist. But simply performs a lookup among existing sessions and returns either a port number if the session is found, or an error code indicating that there is no session with such id. Upon receiving a positive reply from the rtpproxy, SIP Proxy (Sippy B2BUA, OpenSIPS or Kamailio) replaces media IP:port in the SDP body of that SIP reply, in order to point to the rtpproxy and then forwards this reply as usually.

  3. After the session has been created, the rtpproxy listens on the ports it has allocated for that session and relays UDP packets between the call parties. Initially, packets are sent to the addresses provided by the SIP proxy, i.e. taken from the SDP. Once a packet is received from a party, the proxy "latches" to the source ip:port of that packet and uses it as the address of that party from then on, which allows reaching parties located behind NAT. Latching is not done for parties set up as asymmetric.

Important

A good advantage of the RTPProxy is, that it tracks idle time for each existing session (the period of time within which no packets were relayed) and automatically cleans up a session, idle time of which exceeds the value specified at the command line (60 seconds by default).

Content of the Package

The RTPProxy package by default provides the following components:

  • rtpproxy - main RTP proxy server binary (production build)

  • rtpproxy_debug - Main RTP proxy server binary (debug and profiling build)

  • makeann - Utility to pre-encode prompts and announcements (production build)

  • makeann_debug - Utility to pre-encode prompts and announcements (debug and profiling build, built but not installed)

  • extractaudio - Utility to convert recorded sessions into plain audio files (production build, only built if libsndfile is available)

  • extractaudio_debug - Utility to convert recorded sessions into plain audio files (debug and profiling build, built but not installed)

Installation of RTPProxy

Pre-built RTPProxy

In case you want to use a pre-built RTPProxy package, then use your system's package manager to set this up:

dpkg - for Debian™ based systems (including Ubuntu™), when installing .deb packages.

apt-get / aptitude - for Debian™ based systems (including Ubuntu™), when installing from a repository.

rpm - for RPM based systems, when installing .rpm packages.

pkg - for FreeBSD™ when installing from a repository or packages.

Starting with version 3.2.0, packages for several Debian and Ubuntu releases are attached to each release on the GitHub releases page.

Official Docker images are published to Docker Hub (sippylabs/rtpproxy) and GitHub Container Registry (ghcr.io/sippy/rtpproxy) with every release and every commit to the master branch.

In case you want to compile RTPProxy from sources, use the following section "Compilation from sources".

Compilation from sources

Installing Dependencies

The RTPProxy package has several optional dependencies that are detected at the configure stage and an appropriate functionality is enabled.

Those dependencies and their impact on the RTPProxy build are listed below:

  • bcg729 - Enables G.729 support in several tools.

    https://github.com/BelledonneCommunications/bcg729

    Affected binaries: extractaudio makeann

  • libsndfile - Required to build extractaudio, which uses it to save decoded audio in a number of popular audio formats.

    https://libsndfile.github.io/libsndfile/

    Affected binaries: extractaudio

  • libg722 - Enables G.722 support in several tools.

    https://github.com/sippy/libg722

    Affected binaries: extractaudio makeann

  • libsrtp2 (preferred) or libsrtp - Enables decoding recordings of SRTP streams. Together with OpenSSL, also enables building the dtls_gw and ice_lite modules.

    https://github.com/cisco/libsrtp.git

    Affected binaries: extractaudio, dtls_gw, ice_lite

  • libgsm - Enables GSM 06.10 support in several tools.

    Affected binaries: extractaudio makeann

  • libsystemd-dev - allows for a tighter systemd integration.[1]

    libsystemd@github

    Affected binaries: rtpproxy

  • libelperiodic - Required by several internal tools that are used by an automated testing framework. If it is not installed on the system, the copy bundled as a git submodule is built and used instead.

    https://github.com/sobomax/libelperiodic.git

    Affected tools: rtpp_notify_client.py rtpp_query.py getmonotime.py

  • tcpdump - Test framework requires it to capture traffic and verify PCAP decoding features. make check will fail if it's not installed.

    http://www.tcpdump.org/release/

  • sudo - Test framework requires it to run the tcpdump at elevated privileges. make check will fail if it's not installed.

    http://www.sudo.ws/

Important

Do not forget about your compiler! To build this version of the RTPProxy you would need either:

  • GCC compiler, version 11 or higher, or

  • Clang compiler, version 11 or higher

Those are the oldest versions tested by the continuous integration.

Compilation of the Source Code

Clone the source code (into a directory where you want to store compilation sources, usually it's /usr/src/). The master branch contains the latest development version, use a release tag (e.g. v3.2.0) instead to build a stable release:

     cd /usr/src
     git clone -b master https://github.com/sippy/rtpproxy.git

Make sure you bring in the RTPProxy git submodules:

     git -C rtpproxy submodule update --init --recursive

Now compile and install it using the following commands:

A sequence for users:

     cd rtpproxy
     ./configure
     make clean all
     make install

Developers might also want to include make check in the sequence. Make sure you have libelperiodic, sudo and tcpdump installed for that step to complete:

     cd rtpproxy
     ./configure
     make clean all
     make check
     make install

After a successful compilation, you have the following binaries

  • Main RTP proxy server binary - production build.

    After the compilation is done, you have a binary file stored in the /usr/local/bin/ folder. You can test this out, checking the version of the rtpproxy: /usr/local/bin/rtpproxy -V

  • Main RTP proxy server binary - debug and profiling build.

    You will also have a second binary file for debug and profiling: /usr/local/bin/rtpproxy_debug



[1] Also requires --enable-systemd configure argument to enable. See section on enabling systemd support for details.

Getting Started

A good way to maintain your RTPProxy is to start it using a certain Service Manager, such as systemd.

systemd is the default service manager on most Linux distributions:

  • Ubuntu 15.04 is the first version (of Ubuntu) that uses systemd by default;

  • Debian 8 (Jessie) is the first version (of Debian) that uses systemd by default;

  • It has been the default system and service manager in Red Hat Enterprise Linux and CentOS since version 7, and in Fedora since Fedora 15;

  • There is a huge list of other distributions, that use the systemd as a System Manager by default now as well (OpenSUSE, Mint, Mageia etc.) .

In case you have a system, that doesn't support the systemd, you can use any other Service Managers as you want to (e.g. init.d start scripts in Debian systems older than 8th one).

That said, systemd is the recommended solution, because it provides the following capabilities:

  • Define a list of options in a separate environment file, and use those options as a variable in the start row 'ExecStart='

  • You're able to react on a failure of an application using a special parameter 'OnFailure=' (and to send notification for e.g.)

  • You can specify a list of pre-actions to be performed, before systemd launches binary file 'ExecStartPre=' (e.g. creation of /var/run/rtpproxy directory and adding user/group rights to it)

  • As well as you can add pre-start actions, you can perform post-stop actions for cases when application stops running 'ExecStopPost=' (e.g. deletion of the /var/run/rtpproxy directory)

  • You can set a policy for cases when application stops improperly (not a manual stop of the application), e.g. restarting always with 'Restart=always'

  • You may also want to add a time delay between restart attempts with 'RestartSec=5'

  • You can deal with different things related to logging or syslogging (e.g. changing of a name identifier when writing to a syslog 'SyslogIdentifier=' or specifying a different logfacility level with 'StandardOutput=' and 'StandardError=' )

  • User/Group to be used for running an application

  • and many many other things..

The link to an instance of the systemd service file (for the RTPProxy): systemd configuration file instance -> Instance of the systemd service file

After you downloaded your systemd service file, adjust OPTIONS parameters.

Set your own IP address for listening, or leave it with 0.0.0.0 (manual way)

-l 0.0.0.0

Set upper bound on number of file descriptors the rtpproxy service instance might be using. Rule of thumb is that 4 file descriptors are needed for a fully established session (two RTP endpoints and two RTCP ones).

Important

Make the value reasonably high to match your load expectations in terms of number of concurrent sessions but not excessively high, as it may cause rtpproxy to fail initialization because operating system usually also enforces upper bound on this value that varies from system to system:

-L 4096

You may also want to change a port range for opening UDP ports:

-m <min number> -M <max number>

e.g.: -m 10000 -M 40000

Change your log facility level if you want to:

-d INFO:<LOG_LOCAL_NUMBER>

e.g.: -d INFO:LOG_LOCAL5
      -d DBUG:LOG_LOCAL5

Another great reason to use systemd, is a possibility of systemd to create control sockets for rtpproxy.

The benefit of this is that the control socket stays in place while rtpproxy is down for some reason, e.g. being restarted. Clients (such as OpenSIPS or Kamailio) connecting to it during that time are not refused: their connections are queued and get served once rtpproxy is back (they can still get timeout errors if that takes too long). Connections that were already open when rtpproxy went down are lost.

To let this be working, follow this guide: Configuration section -> 5. Configuration of control socket.

Other than that, for better view of how to configure your startup line, you can use this Configuration section.

After you're done with customizing of your systemd service file for RTPProxy, follow this sequence to finish it:

  • copy it to the /etc/systemd/system/ directory

  • reload systemd configuration with:

    bash> sudo systemctl daemon-reload
  • enable the service:

    bash> sudo systemctl enable rtpproxy.service
  • Now you can start your RTPProxy using the commands given below

Working with a systemd, you may need to know a list of general commands to manage with your RTPProxy service, here they are:

  • Start of the service:

    bash> sudo systemctl start rtpproxy.service
  • Stop of the service:

    bash> sudo systemctl stop rtpproxy.service
  • Restart of the service:

    bash> sudo systemctl restart rtpproxy.service
  • Check status of the service:

    bash> sudo systemctl status rtpproxy.service
  • Show systemd configuration file of the service:

    bash> sudo systemctl show rtpproxy.service

To see an output after you started your RTPProxy, or even just to keep a track on how it's working, you can use the following commands:

  • Look into a journal:

    bash> sudo journalctl -xe -u rtpproxy.service
  • You can configure logging to a separate logfile, to do this, follow the manual (or you can use manual logging to a syslog file) listed in the:

    Configuration section -> Logging to a separate file

  • You can see an output of the systemctl tool:

    bash> sudo systemctl status rtpproxy.service

Important

Interaction of a SIP proxy server with RTPProxy application is supported via

rtpproxy.sock - control socket created by the RTPProxy

rtpproxy_timeout.sock - a socket for timeout notifications created by the SIP proxy

Configuration

Historically, primary configuration method for the RTPProxy was done via command-line parameters. Starting with version 2.2 certain advanced parameters (particularly loadable modules configuration) have been moved into configuration file.

Command Line Parameters

SYNOPSIS

rtpproxy [-?] [-2] [-f] [-v] [-V] [-R] [-l addr1[/addr2]...] [-l [label@]address[=advertized_address]...] [-6 addr1[/addr2]...] [-6 [label@]address[=advertized_address]...] [-s ctrl_socket] [-t tos] [-p pidfile] [-T max_ttl] [--maxpps pps[,rtcp_pps]] [--maxpsize size[,rtcp_size]] [-r rdir [-S sdir]] [-L nofile_limit] [-A advaddr1[/advaddr2]] [-m min_port] [-M max_port] [-u uname[:gname]] [-w sock_mode] [-F] [-i] [-n timeout_socket] [-P] [-a] [-d log_level[:log_facility]] [-W setup_ttl] [--force_asymmetric] [-b] [-c fifo|rr] [--nice level] [-D] [--no_redirect] [--config config_file] [--dso module...] [--bridge_symmetric] [--no_resolve]

OPTIONS

The following command-line parameters are supported:

-?

Show summary of options.

-2

Send every RTP packet twice in sessions that use low-bitrate codecs. Only packets that are smaller than 128 bytes will be sent twice. This option can improve audio quality on lossy links.

-f

rtpproxy will stay in foreground mode if this option is set.

-V

Show version of program.

-v

Show supported rtpp command protocol revisions.

-l addr1[/addr2]

IPv4 listen IP address(es). This option can be specified multiple times. Bridging mode can be enabled when the first IPv4 (or first IPv6) listen argument is specified as addr1/addr2.

Additional labeled listen addresses use the separate format [label@]address[=advertized_address]. The /addr2 form is not valid for labeled entries.

When multiple listen addresses are configured, all addresses after the first must have unique labels. Such labels are accepted by the l{label} argument in U and L commands.

-6 addr1[/addr2]

IPv6 listen IP address(es). This option can be specified multiple times. Two argument formats are supported: addr1[/addr2] and [label@]address[=advertized_address].

-s ctrl_socket

This parameter configures rtpproxy control socket. The control socket is used by the call controller for the purpose of creating, modifying, and deleting RTP sessions. The control socket can also be used to fetch stats from the rtpproxy process, or about specific media sessions. Format of ctrl_socket is <type>:<socket>. Following types are supported:

  • udp: Create UDP control socket. In this mode rtpproxy will listen on a UDP socket for control messages from the call controlle.

    Example: -s udp:127.0.0.1:9000

    IP address can be '*' in which case rtpproxy will listen on all local interfaces. If port is omitted then port 22222 will be used.

    Note

    rtpproxy control protocol has no built-in security mechanisms. Make sure that you protect the listening IP and port properly when using rtpproxy with UDP control socket.

  • udp6: Create IPv6 UDP control socket. In this mode rtpproxy will listen on UDP/IPv6 for control messages from the SIP Controller.

    Example: -s udp6:::1:9000

  • tcp: Create IPv4 TCP control socket. In this mode rtpproxy will listen on TCP/IPv4 for control messages from the SIP Controller.

    Example: -s tcp:192.168.0.1:9001

  • tcp6: Create IPv6 TCP control socket. In this mode rtpproxy will listen on TCP/IPv6 for control messages from the SIP Controller.

    Example: -s tcp6:::1:9002

  • unix: Create UNIX domain socket in a datagram-like mode for control interface. In this mode the SIP Controller and rtpproxy must be running on the same host. This is traditional mode when rtpproxy would close connection after processing each command and sending reply.

    Example: -s unix:/var/run/rtpproxy.sock

  • cunix: Create UNIX domain socket for control interface. In this mode the SIP Controller and rtpproxy must be running on the same host. Similar to unix: above, but the rtpproxy would keep control socket open and accept multiple commands via the same connection.

    Example: -s cunix:/var/run/rtpproxy.sock

  • fd: Use socket N, inherited from the parent process, instead of creating one. The type of the control socket is determined from the socket: a datagram IPv4/IPv6 socket is used as udp: / udp6:, a listening IPv4/IPv6 or UNIX domain stream socket as tcp: / tcp6: / cunix:, and a connected stream socket as a single control channel to the SIP Controller. The rtpproxy does not change the ownership or permissions of such a socket, and does not remove its path on exit.

    Example: -s fd:3

Default value is unix:/var/run/rtpproxy.sock.

-t tos

Set ToS (Type of Service) in the outgoing IP header. Default value is 0xB8. Setting this parameter to -1 disables setting ToS resulting in operating system default ToS being used instead.

-r rec_dir

Directory to write recorded RTP sessions.

-S spool_dir

Spool directory for recording of RTP streams. When the session is stopped, the recording will be moved from the spool directory to the rec_dir directory as specified by the -r option.

-R

Prevent rtpproxy from recording RTCP when recording RTP. rtpproxy records RTCP by default when RTP recording is enabled.

-p pid_file

This parameter configures the name of the file where PID of running rtpproxy will be stored. Default is /var/run/rtpproxy.pid.

-T max_ttl

Specify the RTP inactivity timer. Defaults to 60 seconds.

If the rtpproxy does not receive any RTP packets for more than max_ttl it will then delete the session.

--maxpps pps[,rtcp_pps]

Set the default per-stream packet-per-second limit for newly created RTP/RTCP streams. The first value applies to RTP streams. The optional second value applies to RTCP streams; if omitted, the RTP value is used for RTCP as well. A value of 0 disables the default limit, which is the default behavior. The limit can be changed for an existing session using the S pps= control sub-command.

--maxpsize size[,rtcp_size]

Set the default maximum allowed incoming packet size in bytes for newly created RTP/RTCP streams. The first value applies to RTP streams. The optional second value applies to RTCP streams; if omitted, the RTP value is used for RTCP as well. Packets larger than the configured size are dropped. A value of 0 disables the default limit, which is the default behavior. The limit can be changed for an existing session using the S mps= control sub-command.

-L nofile_limit

Set the maximum number of open file descriptors per process. The default maximum is set by the operating system, and can be overridden using the -L flag.

The rtpproxy requires four file descriptors per session to ensure that it can reliably identify where each stream is coming from in a NAT firewall scenario.

-A advaddr1[/advaddr2]

Set advertised address of rtpproxy. Useful if the rtpproxy is behind a NAT firewall. (Amazon EC2) When the rtpproxy receives a session request from a SIP controller it will return the IP address(es) specified by the -A option.

This option configures the legacy bridge internal/external address pair. For per-address advertised overrides with multiple -l/-6 entries, use =advertized_address inside each listen address argument.

-m min_port

Set lower limit on UDP ports range that the rtpproxy uses for RTP/RTCP sessions to min_port. Default is 35000.

-M max_port

Set upper limit on UDP ports range that the rtpproxy uses for RTP/RTCP sessions to max_port. Default is 65000.

-u uname[:gname]

Switch rtpproxy to UID identified by the uname and optional GID identified by gname when proxy is up and running.

-w sock_mode

Set access mode for the controlling UNIX-socket (if used). Only applies if rtpproxy runs under a different GID using -u option.

-F

By default the rtpproxy will warn user if running as superuser (UID 0) in local control mode and refuse to run in remote control mode at all. This switch removes the check.

-i

Enable independent RTP activity timeout mode. By default, a timeout (which results in automatic destruction of the session) can only occur if no RTP packets are received on any of the session's ports. This option, if set, varies that behaviour, such that a timeout will occur if packets are still being received on one port but not the other. The option should be used with caution since in some cases it's perfectly fine to have packets coming from only one side of conversation (i.e. when the second party has muted its audio).

-n timeout_socket

This parameter specifies permitted notification sockets only. The listening socket must be created by another application, preferably before starting rtpproxy.

Timeout notifications must be enabled by the SIP controller when setting up the session. The SIP Controller must specify the timeout_socket, and a notify_tag, which is expected to be an arbitrary string that can be used by the SIP controller to identify which session a received time out notification relates to.

If a SIP Controller specifies a notification socket for a session, and that socket is not specified using the -n flag, the rtpproxy will not send a notification, and will not produce an error. It will ignore the notification request.

Format of timeout_socket is <type>:<socket>. Following types are supported:

  • unix: Connect to UNIX domain socket for sending timeout notifications. In this mode B2BUA and rtpproxy must be running on the same host.

    Example: -n unix:/var/run/rtpproxy_timeout.sock

  • tcp: Connect to a remote host using TCP/IP for sending timeout notifications. Format of the socket parameter in this case is <host>:<port>.

    Example: -n tcp:10.20.30:12345

There is no default value, notifications are not sent and not permitted unless a value is specified explicitly. Multiple notification sockets can be provided by specifying the -n flag more than once.

-P

Record sessions using libpcap file format instead of non-standard ad-hoc format. The libpcap format, which is the de-facto standard for packet capturing software, has the advantage of being compatible with numerous third-party tools and utilities, such as tcpdump or Wireshark. The drawback of libpcap is slightly larger overhead (extra 12 bytes for every saved RTP packet for IPv4).

-a

Record all sessions going through the rtpproxy unconditionally. By default rtpproxy expects the SIP controller to enable recording on a per-session basis.

-d log_level[:log_facility]

Configures the verbosity level of the log output. Possible log_level values in the order from the most verbose to the least verbose are: DBUG, INFO, WARN, ERR and CRIT.

The optional log_facility parameter sets syslog(3) facility assigned to log messages.

Example: -d WARN:LOG_LOCAL5

The default level in foreground mode is DBUG, in background - WARN and facility is LOG_DAEMON.

--force_asymmetric

Treat all RTP/RTCP sessions as "asymmetric", i.e. disable any NAT traversal features unconditionally.

-W setup_ttl

Specify the inactivity timer for sessions that have not been fully set up yet, i.e. before the rtpproxy has received the matching L command. Defaults to the value of -T.

-b

Allocate UDP ports for new sessions sequentially instead of randomly. Mostly intended for debugging, to make the behaviour more predictable.

-c fifo|rr

Switch the rtpproxy process to the SCHED_FIFO (fifo) or SCHED_RR (rr) real-time scheduling policy, at the maximum priority for that policy.

--nice level

Set the scheduling priority (nice value) of the rtpproxy process to level.

-D

Do not change the working directory to / when switching into the background mode.

--no_redirect

Do not redirect the standard output and the standard error to /dev/null when switching into the background mode.

-C

Print the name of the monotonic clock used by the rtpproxy and exit.

--config config_file

Read the configuration of the loadable modules from config_file, in the UCL format. An example is provided in doc/rtpproxy.conf.sample in the source tree.

--dso module

Load module, specified as a path to the shared object of a dynamically loadable module, or as a name of a module compiled in statically. This option can be specified multiple times. Modules that require configuration cannot be loaded this way, use --config instead.

--bridge_symmetric

In the bridging mode, do not treat all sessions as asymmetric, which is the default in that mode for historical reasons.

--no_resolve

Only accept numeric IP addresses, in the command line arguments and in the control commands, instead of resolving host names.

Configuration File

Configuration of the loadable modules is read from a configuration file in the UCL format, specified with the --config command-line option. See Modules for the available modules and their parameters. An example is provided in doc/rtpproxy.conf.sample.

Startup Script

The way you integrate RTPProxy into your system startup sequence is highly OS-version dependent. If you are using a pre-built RTPProxy package then most likely such method will be provided as part of that package and the best course of actions is to use that facility.

However, if you are a package maintainer or system builder you might need to create such configuration from scratch. In the next sections we would provide some basic guidelines on how to launch RTPProxy in some common cases.

Systemd based systems

On most modern Linux-based systems systemd is the recommended method to start the RTPProxy.

[Unit]
Description=RTPProxy media server
After=network.target
Requires=network.target

[Service]
Type=simple
PIDFile=/var/run/rtpproxy/rtpproxy.pid
Environment='OPTIONS= -f -L 4096 -l 0.0.0.0 -m 10000 -M 20000 -d INFO:LOG_LOCAL5'

Restart=always
RestartSec=5

ExecStartPre=-/bin/mkdir /var/run/rtpproxy
ExecStartPre=-/bin/chown rtpproxy:rtpproxy /var/run/rtpproxy

ExecStart=/usr/local/bin/rtpproxy -p /var/run/rtpproxy/rtpproxy.pid -s unix:/var/run/rtpproxy/rtpproxy.sock \
 -u rtpproxy:rtpproxy -n unix:/var/run/rtpproxy/rtpproxy_timeout.sock $OPTIONS
ExecStop=/usr/bin/pkill -F /var/run/rtpproxy/rtpproxy.pid

ExecStopPost=-/bin/rm -R /var/run/rtpproxy

SyslogIdentifier=rtpproxy
SyslogFacility=local5

TimeoutStartSec=10
TimeoutStopSec=10

[Install]
WantedBy=multi-user.target

The source tree also contains the unit files used for packaging, rpm/rtpproxy.service and rpm/rtpproxy.socket, which are installed by the Debian packages. They use systemd socket activation, which requires building with --enable-systemd, see below.

Logging

Logging is a very important aspect of the RTPProxy operation that should not be overlooked. In our experience, properly configured and tested RTPProxy instance can keep running unattended literally for many years non-stop. Yet, various issues with a particular session and/or endpoint would inevitably pop-up over time, so it's of utter importance to setup logging and log management in a manner that is durable and forward-looking. Allowing someone to perform a post-mortem investigation of particular issue that happened days, if not weeks, ago with a relative ease.

Some of the considerations one should have in mind while configuring logging is:

  1. How much storage my system has for the RTPProxy to use?

  2. What is the typical amount of time it takes for my user to report an issue and get it escalated to the DevOps team?

  3. What is the typical amount of time it takes for the DevOps team to act on issue and look at the logs in question?

  4. How much detail do I need? The answer to this might depend on whether this is a development/QA or a production system.

  5. Are there any other non-technical considerations that might influence those decisions? For example: data protection laws, law enforcement requirements etc.

Logging to a separate file

  • First of all you need to create a file you will use for logging and give it proper permissions (according to a user/group starting RTPProxy):

    touch /var/log/rtpproxy.log
    chown rtpproxy:rtpproxy /var/log/rtpproxy.log
  • Then define a log facility for it in rsyslog configuration file (add a row at the bottom of the file) and restart rsyslog server:

    vim /etc/rsyslog.d/50-default.conf
     local5.*        /var/log/rtpproxy.log
    
    systemctl restart rsyslog.service
  • next step add a log rotation configuration for this log file (that's a good way of handling overfilled log files):

    vim /etc/logrotate.d/rtpproxy
    /var/log/rtpproxy.log {
      missingok
      compress
      delaycompress
      notifempty
      copytruncate
      rotate 5
      daily
    }
  • Now add a log facility level to your start row of the RTPProxy:

    -d INFO:LOG_LOCAL5
  • Reload systemd configuration (to apply previous change) and restart your RTPProxy server. Now logging should be done using new output file:

    systemctl daemon-reload
    
    systemctl restart rtpproxy.service

Configuration of control socket (using systemd)

As it was said earlier, keeping of the control socket using systemd, is a good reason to start using systemd.

A configuration process is pretty simple, there are just a few steps you need to implement to start using of it.

1. Firstly, you need to install package requirements: libsystemd-dev

2. Secondly, you need to compile rtpproxy with a systemd flag (a usual compilation process, just add an option to a configure execution):

./configure --enable-systemd

3. A third step, you need to create rtpproxy.socket systemd unit file, that will be responsible for keeping your control socket:

An example:

/etc/systemd/system/rtpproxy.socket (enabling it with systemctl enable creates the link in sockets.target.wants)

[Socket]
ListenStream=/var/run/rtpproxy/rtpproxy.sock
SocketUser=rtpproxy
SocketGroup=rtpproxy
SocketMode=0660
ExecStartPost=-/bin/chown rtpproxy:rtpproxy /var/run/rtpproxy

[Install]
WantedBy=sockets.target

The SIP proxy has to be able to write to the socket, so make sure it runs as a member of the rtpproxy group.

4. Set a parameter in startup row of rtpproxy.service unit (you don't need to provide a path to socket file):

-s systemd:

an example:

ExecStart=/usr/local/bin/rtpproxy -p /var/run/rtpproxy/rtpproxy.pid -s systemd: -u rtpproxy:rtpproxy -n unix:/var/run/rtpproxy/rtpproxy_timeout.sock -f -l 0.0.0.0 -m 10000 -M 20000 -d INFO:LOG_DAEMON

When you're done with all above, do not forget to reload systemd configuration and enable socket unit:

bash> sudo systemctl daemon-reload

bash> sudo systemctl enable rtpproxy.socket

(btw here is a nice article of how to configure systemd socket unit files - http://0pointer.de/blog/projects/socket-activation.html)

Now start rtpproxy.socket and rtpproxy.service just after that:

bash> sudo systemctl start rtpproxy.socket

bash> sudo systemctl start rtpproxy.service

At this stage, your rtpproxy.service should catch up a socket created by systemd and start working properly. If you see some kind of errors, then you're missing one of the steps defined above.

Modules

Some of the RTPProxy functionality is implemented as loadable modules. A module can be loaded either via the configuration file, which also allows setting its parameters, or via the --dso command-line option, which only works for modules that take no parameters.

Loading Modules

Each module is configured in its own block of the modules section of the configuration file. The name of the block is the name of the module. Unless the load parameter specifies the path to the module's shared object, the module is loaded from libdir/rtpproxy/rtpp_name.so (rtpp_name_debug.so for the debug build). Modules that take no parameters can have an empty block:

modules {
    acct_rtcp_hep {
        capt_host = 192.0.2.10
    }

    catch_dtmf {
    }
}

The same module can be loaded more than once, each loaded copy being a separate instance, numbered from 1 in the order of loading.

Controlling Modules

Modules that are controlled on a per-session basis accept sub-commands appended to the U or L command after &&, in the form Mmodule_id:instance arguments. The module IDs are listed below, and instance is 1 unless the module is loaded more than once. For example, the following command sends S to the first instance of the dtls_gw module:

U call-id 192.0.2.1 12345 from-tag && M4:1 S

Several sub-commands can be chained in the same command, each with its own &&. The output of each of them follows the reply to the main command, separated by && too. A failed sub-command is reported as a negative number, while one with no output is reported as 0, or omitted when no sub-command after it has any output.

Table 1. Modules

ModuleIDPurpose
acct_csv1Per-session accounting into a CSV file
acct_rtcp_hep2Reporting RTCP to a HEP (Homer) collector
catch_dtmf3Notifying the call controller of DTMF events
dtls_gw4Gateway between plain RTP and DTLS-SRTP
ice_lite5ICE-Lite and RTCP multiplexing

acct_csv

Writes one record with the session's statistics into a CSV file for each session, when the session ends. A header line with the column names is written when the file is created. The columns include the Call-ID, the from-tag, the session setup and teardown times, and the packet, loss, jitter and SSRC statistics for each direction. The file is re-opened if it is moved or removed, e.g. by log rotation.

The module takes no sub-commands. The parameters are:

logfile

Path of the CSV file. A relative path is relative to the directory the rtpproxy was started from. Default: rtpproxy_acct.csv.

Since the parameter is optional, the module can also be loaded with the --dso command-line option.

modules {
    acct_csv {
        logfile = /var/log/rtpproxy/rtpproxy_acct.csv
    }
}

acct_rtcp_hep

Reports every RTCP packet received, converted to JSON, to a HEP (Homer) collector, using HEPv3.

The module takes no sub-commands. The parameters are:

capt_host

Address of the collector. Default: 10.0.0.1.

capt_port

Port of the collector. Default: 9060.

capt_ptype

Transport, udp or tcp. Default: udp.

capt_id

Capture agent ID, from 0 to 4294967295. Default: 101.

modules {
    acct_rtcp_hep {
        capt_host = 192.0.2.10
        capt_port = 9060
        capt_ptype = udp
        capt_id = 101
    }
}

catch_dtmf

Detects RFC 2833 (RFC 4733) telephone events in the stream and notifies the call controller of each DTMF digit received. Developed in collaboration with the OpenSIPS project. The module takes no parameters.

Detection is enabled per stream with the sub-command:

M3:1 tag [payload_type [h]]
tag

URL-encoded string that is sent back with each notification.

payload_type

RTP payload type of the telephone events. Default: 101.

h

Drop the telephone event packets instead of relaying them.

Notifications are sent the same way as the session timeout notifications, so notifications have to be enabled for the session: the U/L command has to specify the notification socket (permitted with the -n command-line option) and the notification tag. For each digit, when its last packet is received, the following line is sent:

tag digit volume duration side

where duration is in RTP timestamp units, and side is 0 for the caller and 1 for the callee. For example:

U call-id 192.0.2.1 12345 from-tag tcp:192.0.2.100:2222 ntag && M3:1 caller%20dtmf 101

would result in notifications like:

caller dtmf 1 0 960 0

dtls_gw

Gateways between plain RTP (RTP/AVP) and DTLS-SRTP (UDP/TLS/RTP/SAVP) streams, taking care of certificate validation, key negotiation and SRTP encryption/decryption. It can also re-encrypt already encrypted streams back to back, e.g. for recording. Requires the RTPProxy to be built with libsrtp2 (or libsrtp) and OpenSSL. A self-signed certificate is generated when the module starts. The module takes no parameters.

The sub-commands are:

M4:1 S

Allocate a DTLS endpoint on the side facing the encrypted party if needed, and return the session mode and the certificate fingerprint to be put into the SDP.

M4:1 A digest_alg fingerprint [ssrc]

Set up the DTLS endpoint for the remote end in the active or actpass mode, using the fingerprint from its SDP. SSRC verification is enabled if ssrc is given.

M4:1 P digest_alg fingerprint [ssrc]

The same, for the remote end in the passive mode.

M4:1 D

Destroy the DTLS context created by A or P.

M4:1 U

Destroy the DTLS context created by S.

For example, an incoming INVITE with plain RTP answered with DTLS-SRTP by a passive party:

U call-id 192.0.2.1 12345 from-tag && M4:1 S
35000 && actpass SHA-256 4A:AD:B9:...

L call-id 192.0.2.2 23456 from-tag to-tag && M4:1 P SHA-256 2C:7F:01:...
35002

See modules/dtls_gw/README.md for more scenarios.

ice_lite

Implements the lite variant of ICE (RFC 5245), allowing ICE-enabled user agents to establish media sessions through the RTPProxy, and RTCP multiplexing and de-multiplexing. Together with dtls_gw, it allows the RTPProxy to act as a WebRTC endpoint. Requires the same libraries as dtls_gw. The module takes no parameters.

The sub-commands are:

M5:1 S

Allocate an ICE endpoint if needed, and return the local ice-ufrag, ice-pwd and the ICE candidates (URL-encoded) to offer to the user agent.

M5:1 A ice_ufrag ice_pwd

Process an ICE offer received from the user agent, allocating an ICE endpoint if needed.

M5:1 C candidate

Add a single ICE candidate received from the user agent, as in the a=candidate SDP attribute. Repeat for each candidate.

M5:1 D

Destroy the ICE context in the forward direction.

M5:1 U

Destroy the ICE context in the reverse direction.

For example, an incoming INVITE with an ICE offer:

U call-id 192.0.2.1 12345 from-tag && M5:1 A ufrag pwd && M5:1 C 1 1 UDP 2130706431 192.0.2.1 12345 typ host

See modules/ice_lite/README.md for more scenarios.

Development

This chapter is intended for developers who embed the RTPProxy into their own applications, or who work on the RTPProxy itself and need to test their changes.

Embedding RTPProxy: librtpproxy

The whole RTPProxy engine is also available as a library, librtpproxy, which allows running it inside another process, e.g. a test harness, a fuzzer or an application that needs a media relay of its own without managing a separate daemon.

Building and Installing

The library is always built, as it is used by the rtpproxy itself, but it is only installed when the --enable-librtpproxy option is given to the configure script:

./configure --enable-librtpproxy
make
make install

This installs a static librtpproxy.a, which already includes the bundled libraries and the modules, and the headers: librtpproxy.h with the main interface and librtpproxy/packetport.h, librtpproxy/packet_ext.h with the packet port interface described below. On Debian and Ubuntu the same files are provided by the rtpproxy-dev package.

Since the library is static, an application has to link with the libraries that the rtpproxy itself has been configured with as well, such as -lsiplog, -lelperiodic, codec and SRTP libraries, -lssl -lcrypto, -lm and -lpthread. If the RTPProxy has been built with LTO, the application has to be linked using the same compiler.

Interface

The main interface consists of two functions:

#include <librtpproxy.h>

struct rtpp_cfg *rtpp_main(int argc, const char * const *argv);
void rtpp_shutdown(struct rtpp_cfg *);

rtpp_main() takes the same arguments as the rtpproxy command line (see Configuration), with argv[0] being the program name, initializes the engine and starts its threads, then returns right away with a handle to the running instance. The library never daemonizes, does not write a PID file, does not change the current directory and does not install any signal handlers, so the -f option is not needed. Instead of exiting the process, an error in the arguments or during initialization makes rtpp_main() return NULL. The global state of the getopt() is saved and restored, so the application is free to use it as well.

rtpp_shutdown() stops the instance, destroys all of its sessions and releases all resources associated with it.

Controlling an Embedded Instance

An embedded instance is controlled using the same protocol as a standalone one, via any of the control sockets described in Configuration. The most convenient one is usually the fd:N socket, which makes the instance use a socket created by the application: one end of a connected socket pair becomes a private control channel, with no need to pick an address or a path. Over such a channel commands are written one per line and the replies are read back the same way, without the cookies used by the network control sockets:

#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <librtpproxy.h>

int
main(void)
{
    int fds[2];
    char fdarg[16], buf[256];
    struct rtpp_cfg *cfsp;
    ssize_t len;

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0)
        return (1);
    snprintf(fdarg, sizeof(fdarg), "fd:%d", fds[1]);
    const char *argv[] = {"rtpproxy", "-s", fdarg, "-l", "127.0.0.1",
      "-d", "info"};
    cfsp = rtpp_main(sizeof(argv) / sizeof(argv[0]), argv);
    if (cfsp == NULL)
        return (1);

    const char *cmd = "U call1 127.0.0.1 12000 ftag1\n";
    write(fds[0], cmd, strlen(cmd));
    len = read(fds[0], buf, sizeof(buf) - 1);
    if (len > 0) {
        buf[len] = '\0';
        printf("allocated: %s", buf);
    }

    rtpp_shutdown(cfsp);
    close(fds[0]);
    return (0);
}

The fuzzing harnesses in scripts/fuzz are another example of using the library.

Packet Ports

Besides sending and receiving packets via sockets, an embedded instance can exchange them with the application directly, through shared memory packet rings called packet ports. A port is created with rtpp_packetport_ctor(), which takes the capacity of the rings and the policy of flushing the outbound ring (explicitly with rtpp_packetport_flush() or on a timer). The application pushes packets into the instance with rtpp_packetport_push() and friends, and pops the packets sent by the instance with rtpp_packetport_try_pop() and friends.

A port is referenced in the commands by its address, in the RTQ:0xpointer format: as the remote address of a stream, followed by a port tag in place of the port number, to have the instance send the packets of that stream into the port, or as a local address (l modifier) to have the stream receive packets from the port instead of a socket. Packet ports are only available in the library. This interface is a work in progress and may change in the future releases, see src/librtpproxy/packetport.h for the details.

Testing

The RTPProxy comes with an extensive suite of integration tests, located in the tests directory. Each test starts one or more rtpproxy instances, drives them via their control sockets, generates and captures media, and compares the results with the reference ones. The tests are also run by the CI on every change, across several operating systems, compilers and architectures.

Running Tests

All tests are run with:

make check

A subset of the tests can be selected with the TESTS variable, and several tests can be run in parallel, since none of them depends on pre-determined ports:

cd tests
make -j4 check TESTS="forwarding/forwarding1 playback/playback1"

A test can also be run directly as sh tests/group/test, which prints the result of each of its steps. Besides the build dependencies of the RTPProxy, the tests need Python 3 with the sippy and parsimonious modules (see python/tools/requirements.txt), ffmpeg and tcpdump. When the tests are run as root, the rtpproxy and the tools are run as the rtpproxy user, via setpriv.

Writing Tests with RPTL

New tests are best written using the RTPProxy Testing Language (RPTL), a small scripting language interpreted by python/RPTL/rptl_run.py. An RPTL script starts the rtpproxy instances it needs, sends them commands, checks the replies and runs external tools, passing the ports allocated by one step on to the next ones, so that the tests need neither pre-determined ports nor root privileges and can safely run in parallel.

A script is a sequence of statements, one per line. A socket statement defines a control connection, which with an address of stdio: (or udp:, tcp:, unix: and so on, with nothing after the type) starts a managed rtpproxy instance with the given command line arguments. Every other statement either sends a command to the named socket or runs a built-in, such as .eval:, .sleep:, .include:, .foreach: or .exec:, and can capture the result into a variable, which is then referenced as %%NAME%%, or pass it to a checking function. Any failed check stops the script with an error:

# Expects RTPPROXY_BIN to be defined
.eval: 0 -> RES_OK
.eval: E50 -> SESS_NOTFOUND
socket r: stdio: -d info -p /dev/null -> example.rout

r: U call1 127.0.0.1 12000 ftag1 -> validate_port(PORT_O)
r: L call1 127.0.0.1 12002 ftag1 ttag1 -> validate_port(PORT_A)
.echo: allocated ports %%PORT_O%% and %%PORT_A%%
r: D call1 ftag1 -> str_compare(RES_OK)
r: D call1 ftag1 -> str_compare(SESS_NOTFOUND)

.close: r

A test itself is a Bourne shell script, which sets up the environment using the common functions from tests/functions, runs the RPTL script with the RPTL_INT interpreter, passing it the parameters with -D, and then examines the results, e.g. compares the replies logged by the instances with the reference output, or decodes the recorded media with extractaudio and checks its checksum:

#!/bin/sh

BASEDIR="`dirname "${0}"`/.."
. "${BASEDIR}/functions"

${RPTL_INT} -s "${BASEDIR}/example/example.rptl" \
 -D RTPPROXY_BIN="${RTPPROXY}"
report "running example.rptl"

${DIFF} "${BASEDIR}/example/example.output" example.rout
report "checking rtpproxy replies"

When the replies contain dynamically allocated ports, the reference output can be a template that matches any port (see scripts/revtempl.py), or the ports can be replaced with a placeholder before the comparison. The test, together with the files it uses, goes into a directory of its group, and is registered in the Makefile.ami of that group, after which the tests/Makefile.in has to be regenerated with automake.

The complete description of the language is available in python/RPTL/README.md, and the tests in tests/recording, tests/session_timeouts, tests/forwarding and tests/playback are written in RPTL and can be used as examples of more complex scenarios, such as running several instances at once, replaying traffic or capturing streams with external tools.