RTPENGINE(1)

๐Ÿ“ฆ
version 10.5.3.5 ยท 2 versions
, in debian bookworm

๐ŸŒธ RTPENGINE ๐ŸŒธ

okay bestie, sit down, hydrate, because this is the manual page for rtpengine โ€” the NGCP proxy for RTP and other UDP based media traffic. it's giving telecom infrastructure realness and honestly? it ate. ๐Ÿ’…


โœจ SYNOPSIS โœจ

rtpengine --interface=addr...
--listen-tcp|--listen-udp|--listen-ng|--listen-tcp-ng|--listen-http|--listen-https=addr...
[option...]

๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

so the Sipwise NGCP rtpengine is a proxy for RTP traffic and other UDP based media traffic. it's meant to be used alongside the Kamailio SIP proxy, and it's a drop-in replacement for any of the other RTP and media proxies out there. like, any of them. no cap. ๐Ÿซถ


๐Ÿ‘‘ OPTIONS ๐Ÿ‘‘

most of these options are, in fact, genuinely optional (iconic behaviour honestly). but there are two big exceptions and you can NOT skip them:

  • you must specify at least one local IP address with --interface
  • you must give at least one of the --listen-... options

also, hot tip: all of these options can โ€” and like, should โ€” be put in a config file instead of dumping them all on the command line. check --config-file below for the tea. ๐ŸŽ€


  • --help โ€” prints the usage information. that's it, that's the whole thing. ๐Ÿ’–
  • -v, --version โ€” if you call the rtpengine daemon with this, it just prints its version number and exits. no drama, no notes.
  • --codecs โ€” prints a list of supported codecs and exits. codec reveal!! โœจ
  • --config-file=FILE โ€” tells you where the config file lives. this config file is an .ini style config file, and every command-line option listed on this page is also a valid option in the config file. a few rules though, bestie, listen up:
    • for all command-line options, you gotta use the long name version, not the single-character version (so like table, not just t).
    • for boolean options that are either present or not (like no-fallback), you have to use a boolean value in the config file โ€” either true or false.
    • if an option shows up in both the config file and the command line, the command-line value wins. command line is the main character here. ๐Ÿ‘‘
    • options that can be specified multiple times on the command line must only be given once in the config file, with the multiple values separated by semicolons (there's an example in the INTERFACES section below).

and as a special value, you can pass none here to suppress loading of the default config file. slay.

  • --config-section=STRING โ€” picks which .ini style section gets used in the config file. you can have multiple sections chillin' in there, but only one can be used at a time. default value is rtpengine. a section gets started in the config file with square brackets, like [rtpengine]. ๐Ÿงš
  • -t, --table=INT โ€” takes an integer argument and says which kernel table to use for in-kernel packet forwarding. more detail lives in the section on in-kernel operation in the README.md. optional, defaults to zero. and if in-kernel operation is simply not the vibe, you can pass a negative number. ๐Ÿ’€
  • -F, --no-fallback โ€” stops it from falling back to userspace-only operation when the kernel module isn't available. heads up: if you give this option and the kernel module isn't there, startup of the daemon fails with an error. periodt.
  • -S, --save-interface-ports โ€” binds ports only on the first available local interface, of the desired family, of the logical interface. if there are no ports available on any local interface of the desired family, you get an error message.
โš ๏ธ and in this case, ICE will be broken. we said what we said. ๐Ÿ˜ญ
  • -i, --interface=[NAME/]IP[!IP] โ€” specifies a local network interface for RTP. you need at least one, but you can absolutely specify multiple. full details are in the INTERFACES section right below. ๐ŸŒˆ
  • -l, --listen-tcp=[IP:]PORT
  • -u, --listen-udp=[IP46:]PORT
  • -n, --listen-ng=[IP46:]PORT
  • -n, --listen-tcp-ng=[IP46:]PORT

each of these enables one of the 4 available control protocols if given, and each takes either just a port number as its argument, or an address:port pair separated by a colon. at least one of these 3 options must be given.

  • the tcp protocol is obsolete. it was used by old versions of OpenSER and its mediaproxy module. it's provided for backwards compatibility only โ€” vintage, not current. ๐Ÿ™ˆ
  • the udp protocol is what Kamailio's rtpproxy module uses. in this mode, rtpengine can be used as a drop-in replacement for any other compatible RTP proxy.
  • the ng protocol is an advanced control protocol and works with Kamailio's rtpengine module. with this one, the complete SDP body gets passed to rtpengine, rewritten, and passed right back to Kamailio. you also unlock several additional features here โ€” ICE handling, SRTP bridging, etc. ๐Ÿ”ฅ
  • the tcp-ng protocol is literally the ng protocol but transported over TCP.
๐Ÿ’ก recommendation from us to you: specify not only a local port number, but also 127.0.0.1 as the interface to bind to. trust.
  • -c, --listen-cli=[IP46:]PORT โ€” the TCP ip and port to listen on for the CLI (command line interface). ๐Ÿ–ฅ๏ธ
  • -g, --graphite=IP46:PORT โ€” the address of the graphite statistics server.
  • -w, --graphite-interval=INT โ€” how often information gets sent to the graphite server. โฐ
  • --graphite-prefix=STRING โ€” adds a prefix to every graphite line.
  • --graphite-timeout=INT โ€” sets how much time (in seconds) before forcing a failed graphite socket connection, for when the graphite server is filtered out. if set to 0, there are no changes. ๐Ÿ“
  • -t, --tos=INT โ€” takes an integer and, if given, sets the TOS value that should go into outgoing packets. default is to leave the TOS field untouched. a typical value is 184 (Expedited Forwarding). โœจ
  • --control-tos=INT โ€” takes an integer and, if given, sets the TOS value that should be set in the control-ng interface packets. default is to leave the TOS field untouched. this parameter can also be set or listed via rtpengine-ctl. ๐ŸŽ›๏ธ
  • -o, --timeout=SECS โ€” takes a number of seconds; after that many seconds with no media traffic received, a media stream is considered dead. and if all the media streams belonging to a particular call go dead, the whole call gets removed from rtpengine's internal state table. defaults to 60 seconds. ๐Ÿ’€
  • -s, --silent-timeout=SECS โ€” ditto as --timeout, but it applies to muted or inactive media streams instead. defaults to 3600 (one hour).
  • -a, --final-timeout=SECS โ€” the number of seconds since call creation, after which the call is deleted. super useful for limiting the lifetime of a call. you can disable this feature entirely by setting the parameter to 0. by default this timeout is disabled. ๐Ÿ•ฐ๏ธ
  • --offer-timeout=SECS โ€” this timeout (in seconds) is applied to calls which only had an offer but no answer. defaults to 3600 (one hour). the situationship calls, if you will. ๐Ÿ˜ญ
  • -p, --pidfile=FILE โ€” specifies a path and file name to write the daemon's PID number to. ๐Ÿ“
  • -f, --foreground โ€” if given, stops the daemon from daemonizing, meaning it stays in the foreground. extremely useful for debugging. ๐Ÿ”
  • -m, --port-min=INT
  • -M, --port-max=INT

both take an integer as an argument and together they define the local port range from which rtpengine will allocate UDP ports for media traffic relay. defaults are 30000 and 40000 respectively. ๐ŸŒˆ

  • -L, --log-level=INT โ€” takes an integer and controls the highest log level that gets sent to syslog. this is merely the default log level used for logging subsystems (see below) that don't explicitly have a separate log level configured.

the log levels match the ones in the syslog(3) man page. default value is 6, equivalent to LOG_INFO. the highest possible value is 7 (LOG_DEBUG), which logs everything. ๐Ÿ‘€

during runtime, you can decrease the log level by sending the signal SIGURS1 to the daemon, and increase it with the signal SIGUSR2. โœจ

  • --log-level-subsystem=INT โ€” configures a log level for one of the logging subsystems. a logging subsystem that doesn't have a log level configured explicitly takes its default value from the log-level setting described above โ€” with the exception of the internals subsystem, which by default has all logging disabled. ๐Ÿคซ

the full list of logging subsystems can be viewed by pulling up the --help online help. some (if not all) subsystems are:

  • core
  • spandsp (messages generated by SpanDSP itself)
  • ffmpeg (messages generated by ffmpeg libraries themselves)
  • transcoding (messages related to RTP/media transcoding)
  • codec (messages related to codec negotiation)
  • rtcp
  • ice
  • crypto (messages related to crypto/SRTP/SDES/DTLS negotiation)
  • srtp (messages related to RTP/SRTP en/decryption)
  • internals (disabled by default)
  • http (includes WebSocket)
  • control (messages related to control protocols, including SDP exchanges)
  • --log-facilty=daemon|local0|...|local7|... โ€” the syslog facility to use when sending log messages to the syslog daemon. defaults to daemon. ๐Ÿงš
  • --log-facilty-cdr=daemon|local0|...|local7|... โ€” same as --log-facility, except the difference is that only CDRs are written to this log facility. ๐ŸŽ€
  • --log-facilty-rtcp=daemon|local0|...|local7|... โ€” same as --log-facility, except the difference is that only RTCP data is written to this log facility.
โš ๏ธ be careful with this parameter, because there may be a LOT of information written to it. like, a lot a lot. ๐Ÿ’€
  • --log-facilty-dtmf=daemon|local0|...|local7|... โ€” same as --log-facility, except the difference is that only DTMF events are written to this log facility. DTMF events are extracted from RTP packets conforming to RFC 4733, are encoded in JSON format, and get written as soon as the end of an event is detected. ๐Ÿ”ฅ
  • --log-format=default|parsable โ€” picks between multiple log output styles. the default is to prefix log lines with a description of the relevant entity, such as [CALLID] or [CALLID port 12345]. the parsable output style is similar, but makes the ID easier to parse by enclosing it in quotes, like [ID="CALLID"] or [ID="CALLID" port="12345"]. both iconic, honestly. โœจ
  • --dtmf-log-dest=IP46:PORT โ€” configures a target address for logging detected DTMF events. similar to the feature enabled by --log-facilty-dtmf, but instead of writing detected DTMF events to syslog, this sends the JSON payload to the given address as UDP packets. ๐Ÿ“ก
  • --dtmf-log-ng-tcp โ€” if --listen-tcp-ng is enabled, this will send DTMF events to all connected clients encoded in bencode format. ๐ŸŽ‰
  • --dtmf-no-suppress โ€” some RTP clients keep sending audio RTP packets during a DTMF event, which means you get both audio packets and DTMF packets showing up at the same time. by default, when transcoding, rtpengine suppresses audio packets during a DTMF event and will only send DTMF packets until the DTMF event is over. setting this option disables that feature. ๐Ÿ™ˆ

(this is only part 1, bestie โ€” there's more coming, don't go anywhere ๐Ÿ’–)

  • --log-srtp-keys โ€” bestie this one writes your SRTP keys to the error log instead of the debug log. yes, for real. ๐Ÿ’…
  • -E, --log-stderr โ€” log to stderr instead of syslog. only useful when you're also using --foreground, so don't come crying to me when it does nothing on its own. ๐Ÿ˜ญ
  • --split-logs โ€” splits multi-line log messages into separate individual log messages, so every single line gets its own little log line prefix. it's giving organized. โœจ
  • --max-log-line-length=INT โ€” when a log line goes over the character count you put here, it gets chopped into multiple lines. you can set it to a negative value to allow unlimited length log lines (go off), and setting it to zero means "use the default", which is unlimited if you're logging to stderr, or 500 if you're logging to syslog. ๐ŸŽ€
  • --no-log-timestamps โ€” no timestamps on log lines written to stderr. only useful together with --log-stderr, iykyk. ๐Ÿ•
  • --log-mark-prefix=STRING โ€” a prefix that gets added to particular data fields in log files that are deemed sensitive and/or private info. defaults to an empty string. ๐Ÿซถ
  • --log-mark-suffix=STRING โ€” same vibe, but a suffix instead. also defaults to an empty string. ๐Ÿ’–
  • --num-threads=INT โ€” how many worker threads to create, and it has to be at least one, periodt. default is to create as many threads as there are CPU cores available. but if the number of CPU cores can't be determined, or if it's less than four, then the default is four. โญ
  • --media-num-threads=INT โ€” number of threads to launch for media playback. defaults to the same number as num-threads. you can set it to zero if you don't want any media playback functionality at all.

okay so media playback is actually handled by two threads: one for reading and decoding the media file, and another to schedule and send out RTP packets. so like, if you set this option to 4, you get 8 threads launched in total. math is math. ๐Ÿ”ฅ

  • --thread-stack=INT โ€” sets the stack size of each thread to the value given in kB. defaults to 2048 kB. you can set it to -1 to leave the default provided by the OS unchanged. ๐Ÿงš
  • --sip-source โ€” storytime: the original rtpproxy, and older versions of rtpengine, by default did not honour IP addresses given in the SDP body, and instead used the source address of the received SIP message as the default endpoint address. newer versions of rtpengine reverse this behaviour and honour the addresses given in the SDP body by default. this option restores the old behaviour. so it's lowkey a time machine. โณ
  • --dtls-passive โ€” enables the DTLS=passive flag for all calls, unconditionally. no cap. ๐ŸŒธ
  • -d, --delete-delay=INT โ€” deletes the call from memory after the specified delay. you can set it to zero for immediate call deletion. poof, gone. ๐Ÿ’€
  • -r, --redis=[PW@]IP:PORT/INT โ€” connects to the specified Redis database (with the given database number) and uses it for persistence storage. the format is ADDRESS:PORT/DBNUM, for example 127.0.0.1:6379/12 to connect to the Redis DB number 12 running on localhost on the default Redis port.

if your Redis database is protected with an authentication password, you can supply it by prefixing the argument value with the password, separated by an @ symbol, like foobar@127.0.0.1:6379/12.

โš ๏ธ heads up bestie โ€” that leaves the password visible in the process list, which poses a security risk if untrusted users access the same system. ๐Ÿ˜ฌ

as an alternative, the password can also be supplied in the shell environment through the environment variable RTPENGINE_REDIS_AUTH_PW.

on startup, rtpengine will read the contents of this database and restore all calls stored therein. during runtime operation, rtpengine will continually update the database's contents to keep it current, so that in case of a service disruption, the last state can be restored upon a restart.

when this option is given, rtpengine will delay startup until the Redis database adopts the master role (but see below ๐Ÿ‘€).

  • -w, --redis-write=[PW@]IP:PORT/INT โ€” configures a second Redis database for write operations. if you give this option in addition to the first one, then the first database gets used for read operations (i.e. to restore calls from), while the second one gets used for write operations (to update states in the database). read and write, living their separate lives. ๐Ÿ’…

for password protected Redis servers, the environment variable for the password is RTPENGINE_REDIS_WRITE_AUTH_PW.

when both options are given, rtpengine will start and use the Redis database regardless of the database's role (master or slave).

  • -k, --subscribe-keyspace=INT โ€” list of redis keyspaces to subscribe. if this isn't present, no keyspaces are subscribed (that's the default behaviour). further subscriptions could be added/removed via rtpengine-ctl ksadd/ksrm. this may lead to enabling/disabling of the redis keyspace notification feature. ๐Ÿฆ‹
  • --redis-num-threads=INT โ€” how many redis restore threads to create. default is 4. ๐ŸŒˆ
  • --redis-expires=INT โ€” expire time in seconds for redis keys. default is 86400. โฐ
  • --active-switchover โ€” with this enabled, any activity (like signalling or media) on a call that was created through a Redis keyspace notification will make rtpengine take control of that call. without this option, an explicit command is required for rtpengine to take (or relinquish) control of a call. main character energy tbh. ๐Ÿ‘‘
  • -q, --no-redis-required โ€” when this parameter is present, or NO_REDIS_REQUIRED='yes' or '1' in the config file, rtpengine starts even if there's no initial connection to redis databases (either to -r or to -w or to both redis).
๐Ÿซ  be aware that if the -r redis cannot be initially connected, sessions are not reloaded upon rtpengine startup, even though rtpengine still starts.
  • --redis-allowed-errors โ€” if this parameter is present and has a value >= 0, it configures how many consecutive errors are allowed when communicating with a redis server before the redis communication gets temporarily disabled for that server. while communication is disabled there will be no attempts to reconnect to redis or send commands to that server. default value is -1, meaning this feature is disabled. this parameter can also be set or listed via rtpengine-ctl. ๐ŸŽ€
  • --redis-disable-time โ€” configures the number of seconds redis communication is disabled because of errors. works together with the redis-allowed-errors parameter. the default value is 10. this parameter can also be set or listed via rtpengine-ctl. โฒ๏ธ
  • --redis-cmd-timeout=INT โ€” if set to a non-zero value it sets the timeout, in milliseconds, for each command to the redis server. if redis doesn't reply within the specified timeout, the command fails. the default value is 0, meaning the commands will be blocking without timeout. this parameter can also be set or listed via rtpengine-ctl; note that setting the parameter to 0 will require a reconnect on all configured redis servers. ๐Ÿ™ˆ
  • --redis-connect-timeout=INT โ€” sets the timeout value, in milliseconds, when connecting to a redis server. if the connection can't be made within the specified timeout, the connection fails. note that in case of failure, when reconnecting to redis, a PING command is issued before attempting to connect, so the --redis-cmd-timeout value will also be added to the total waiting time. this is useful if you're using --redis-allowed-errors, when attempting to estimate the total lost time in case of redis failures. the default value for the connection timeout is 1000ms. this parameter can also be set or listed via rtpengine-ctl. ๐Ÿ’œ
  • -b, --b2b-url=STRING โ€” enables and sets the URI for an XMLRPC callback to be made when a call is torn down due to packet timeout. the special code %% can be used in place of an IP address, in which case the source address of the originating request (or alternatively the address specified using the xmlrpc-callback ng protocol option) will be used. ๐Ÿ”ฅ
  • -x, --xmlrpc-format=INT โ€” selects the internal format of the XMLRPC callback message for B2BUA call teardown. 0 is for SEMS, 1 is for a generic format containing the call-ID only, 2 is for Kamailio. pick your fighter. โš”๏ธ
  • --max-sessions=INT โ€” limits the number of maximum concurrent sessions. set at startup via MAX_SESSIONS in the config file. set at runtime via the rtpengine-ctl util. setting rtpengine-ctl set maxsessions 0 can be used in draining rtpengine sessions.
    • enable feature: MAX_SESSIONS=1000
    • enable feature: rtpengine-ctl set maxsessions >= 0
    • disable feature: rtpengine-ctl set maxsessions -1

by default, the feature is disabled (i.e. maxsessions == -1). ๐Ÿšฆ

  • --max-load=FLOAT โ€” if the current 1-minute load average exceeds the value given here, reject new sessions until the load average drops below the threshold. ๐Ÿ“‰
  • --max-cpu=FLOAT โ€” if the current CPU usage (in percent) exceeds the value given here, reject new sessions until the CPU usage drops below the threshold. CPU usage is sampled in 0.5-second intervals. only supported on systems providing a Linux-style /proc/stat. ๐Ÿ–ฅ๏ธ
  • --max-bandwidth=INT โ€” if the current bandwidth usage (in bytes per second) exceeds the value given here, reject new sessions until the bandwidth usage drops below the threshold. bandwidth usage is sampled in 1-second intervals and is based on received packets, not sent packets. ๐Ÿ“ถ
  • --homer=IP46:PORT โ€” enables sending the decoded contents of RTCP packets to a Homer SIP capture server. the transport is HEP version 3 and the payload format is JSON. this argument takes an IP address and a port number as its value. ๐Ÿ 
  • --homer-protocol=udp|tcp โ€” can be either udp or tcp, with udp being the default. ๐Ÿ›ฐ๏ธ
  • --homer-id=INT โ€” the HEP protocol used by Homer contains a "capture ID" used to distinguish different sources of capture data. this ID can be specified using this argument. ๐Ÿ†”
  • --recording-dir=FILE โ€” an optional argument to specify a path to a directory where PCAP recording files and recording metadata files should be stored. if not specified, support for call recording will be disabled. ๐ŸŽฅ

rtpengine supports multiple mechanisms for recording calls. see recording-method below for a list. the default recording method pcap is described in this section.

PCAP files will be stored within a pcap subdirectory and metadata within a metadata subdirectory. ๐Ÿ“

the format for a metadata file is (with a trailing newline):

/path/to/recording-pcap.pcap
SDP mode: offer
SDP before RTP packet: 1

first SDP

SDP mode: answer
SDP before RTP packet: 1

second SDP

...

SDP mode: answer
SDP before RTP packet: 100

n-th and final SDP

start timestamp (YYYY-MM-DDThh:mm:ss)
end timestamp   (YYYY-MM-DDThh:mm:ss)

generic metadata

So bestie, notice how there's two whole empty lines between every single logic block of metadata? Yeah, that's the vibe, don't skip them ๐Ÿ’… We write out all the answer SDP, each one separated from the next by exactly one empty line. And that generic metadata at the end? It can be literally any length, any number of lines, go off. โœจ

The metadata files show up in the subdirectory when the call completes, but the PCAP files get written into that subdirectory while the call is being recorded โ€” different timing, same folder, it's giving organized queen ๐ŸŽ€

๐Ÿšจ Okay important one: since call recording this way happens entirely in userspace, in-kernel packet forwarding cannot be used for calls that are currently being recorded. Packet forwarding will therefore be done in userspace only. No cap.

  • --recording-method=pcap|proc โ€” pick which flavor of call recording you want, because there's more than one ๐Ÿ“

The options on the menu rn are pcap and proc. The default is pcap, and that's the one described above (the subdirectory + metadata + pcap file situation).

So proc works differently: it writes metadata files directly into the recording-dir (no subdirectory, periodt), and instead of dumping RTP packet data into pcap files, the packet data gets exposed through a special interface in the /proc filesystem. Some dedicated userspace component has to go grab the packets from that interface โ€” usually a daemon, like recording-daemon, which is included in this repository ๐Ÿซถ

Packet data hangs out in kernel memory until the userspace component retrieves it, but only a limited number of packets per media stream (default 10). If nobody retrieves them in time, they just get discarded ๐Ÿ’€ Which honestly is the point โ€” it means you can flag every single call</em> to be recorded and then let the userspace component decide whether the packet data is useful for anything or not.

And here's the slay: with this recording method, in-kernel packet forwarding is fully supported even for calls that are being recorded. ๐Ÿ‘‘

  • --recording-format=raw|eth โ€” choose the shape of your pcap recording ๐ŸŽ€

In raw format (which is the default), there's no ethernet header. Set it to eth and a fake ethernet header gets added, which makes every packet 14 bytes larger. That's it, that's the whole thing.

  • --iptables-chain=STRING โ€” turn on explicit management of an iptables chain, if that's your thing ๐Ÿ”ฅ

When enabled, rtpengine takes control of the iptables chain you name, and that chain must already exist before you start the daemon โ€” it will not be created for you, bestie. On startup, rtpengine flushes the chain, then adds one ACCEPT rule for each media port (RTP/RTCP) opened. Every rule matches the individual port and destination IP address exactly, and gets created with the call ID as the iptables comment. When the port closes, the rule gets deleted. Cleanup queen โœจ

The whole point of this option is that it lets you build a firewall with a default DROP policy across the entire port range rtpengine uses, and then reference that given iptables chain to only selectively allow the ports that are <em>actually</em> in use. Selective, iconic, we love to see it ๐Ÿ’…

Heads up: this applies only to media ports. It does not apply to any other ports (like the control ports) that rtpengine uses. ๐Ÿ™ˆ
Also heads up, and this one's real: the iptables API is not the most efficient one around and does not lend itself to fast dynamic creation and deletion of rules. If you have a high call volume โ€” especially many call attempts per second โ€” you might experience significant performance impact. This is not a shortcoming of rtpengine, it's a shortcoming of iptables and how its API is implemented in the Linux kernel, fr fr. In that case the recommendation is to add a static iptables rule for the entire media port range instead, and not use this option. ๐Ÿ’€
  • --scheduling=default|...
  • --priority=INT
  • --idle-scheduling=default|...
  • --idle-priority=INT

These four control the various thread scheduling parameters ๐Ÿงš The scheduling and priority settings apply to the main worker threads, while the idle- versions of those settings apply to various lower priority threads, like timer runs.

The scheduling settings take the name of one of the supported scheduler policies. Setting it to default or none is equivalent to not setting the option at all and just leaves the system default in place. The strings fifo and rr refer to realtime scheduling policies. other is the Linux default scheduling policy. batch is similar to other except for a small wake-up scheduling penalty. idle is an extremely low priority scheduling policy. The Linux-specific deadline policy is not supported by rtpengine. And ngl, not all systems necessarily support all scheduling policies โ€” go check your system's sched(7) man page for the details ๐Ÿ“–

The priority settings correspond to the scheduling priority for realtime (fifo or rr) scheduling policies and must be in the range of 1 (low) through 99 (high). For all other scheduling policies (including when no policy is specified at all), the priority settings correspond to the nice value and should be in the range of -20 (high) through 19 (low). Now, not all systems support thread-specific nice values โ€” on a system that doesn't, using these settings might have unexpected results ๐Ÿ˜ญ (Linux <em>does</em> support thread-specific nice values, so you're fine there.) Again: refer to your system's sched(7) man page. โœจ

  • --mysql-host=HOST|IP
  • --mysql-port=INT
  • --mysql-user=USERNAME
  • --mysql-pass=PASSWORD

Config for playing back media files stored in a MySQL (or MariaDB) database ๐Ÿ“ At minimum, mysql-host has to be configured for this to work at all. The others are optional and just default to their respective values from the MySQL/MariaDB client library. Easy.

  • --mysql-query=STRING โ€” the query used to pull media files out of the database ๐Ÿ”

There's no default for this one, which means it's a mandatory configuration if you want media playback from a database. The query string you provide must contain the single format placeholder %llu and must not contain any other format placeholders. The ID value that rtpengine gets in the db-id key of the play media message is what gets dropped in place of that placeholder when querying the database. Cute, right?

An example configuration might look like this:

mysql-query = select data from voip.files where id = %llu
  • --endpoint-learning=delayed|immediate|off|heuristic โ€” pick your algorithm for learning RTP endpoint addresses ๐Ÿง โœจ

The legacy setting is delayed, which waits 3 seconds before committing to an endpoint address โ€” and that address is then learned from the first incoming RTP packet seen after the delay. The immediate setting learns the endpoint address from the first incoming packet seen, no 3-second delay. Using off disables endpoint learning altogether, which is likely breaking clients behind NAT ๐Ÿ’€ so like, be careful. And heuristic includes the 3-second delay too, but ranks the source addresses seen from incoming RTP packets by preference:

  1. If a packet is seen with a source address <em>and</em> port matching the SDP address, that address is used.
  2. Otherwise, if a packet with a matching source address (but a different port) is seen, that address is used.
  3. Otherwise, if a packet with a matching source port (but a different address) is seen, that address is used.
  4. Otherwise, the source address of <em>any</em> incoming packet seen is used.
  • --jitter-buffer=INT โ€” size of the (incoming) jitter buffer, in packets ๐ŸŽ€

A value of zero (which is the default) disables the jitter buffer. And rn, the jitter buffer is only implemented for userspace operation, so keep that in mind.

  • --jb-clock-drift โ€” turns on clock drift compensation for the jitter buffer โฐ slay.
  • --debug-srtp โ€” extra log messages to help you debug SRTP issues ๐Ÿ”๐Ÿ˜ญ

Per-packet details get recorded to the log โ€” sequence numbers, ROC, payloads (both plain text <em>and</em> encrypted), authentication tags, etc. Every RTCP packet is logged this way, while every 512th RTP packet is logged. Only applies to packets forwarded/processed in userspace.

  • --reject-invalid-sdp โ€” with this set, rtpengine refuses to process SDP bodies that couldn't be cleanly parsed, instead of skipping over the parsing error and processing the SDP anyway ๐Ÿ™…โ€โ™€๏ธ

Currently this only affects the processing of SDP bodies that end in a blank line. That's the whole scope, periodt.

  • --listen-http=[IP|HOSTNAME:]PORT
  • --listen-https=[IP|HOSTNAME:]PORT

Turn on listening for HTTP or WebSocket connections, or their TLS-secured counterparts HTTPS and WSS ๐Ÿฆ‹ If you don't specify an interface, the listening socket gets bound to all interfaces.

The HTTP listener supports both HTTP and WS, while the HTTPS listener supports both HTTPS and WSS. Two-for-one, we love efficiency ๐Ÿ’…

If HTTPS/WSS is enabled, you must also provide a certificate using the options below. ๐Ÿ‘‡

  • --https-cert=FILE
  • --https-key=FILE โ€” provide a server certificate and its corresponding private key for the HTTPS/WSS listener, in PEM format ๐Ÿ”
  • --http-threads=INT โ€” number of worker threads for HTTP/HTTPS/WS/WSS ๐ŸŒˆ

If you don't specify it, then the same number as given under num-threads gets used. And if <em>no</em> HTTP listeners are enabled, then no threads are created at all. Efficient.

  • --software-id=STRING โ€” sets a free-form string that identifies this software towards external systems, for example in outgoing ICE/STUN requests โœจ

Defaults to rtpengine-VERSION. And the string gets sanitised โ€” every non-alphanumeric character gets replaced with a dash โ€” so it's universally usable no matter what you throw at it. ๐Ÿ’…

  • --dtx-delay=INT โ€” processing delay in milliseconds to handle discontinuous transmission (DTX) or other transmission gaps ๐ŸŽ€

Defaults to zero (disabled) and only applies to transcoded audio streams. When you enable it, it delays processing of received packets for the specified amount of time (much like a jitter buffer) so that DTX handling gets triggered when a transmission gap happens. The decoder is then instructed to fill in the missing time during a transmission gap โ€” for example by generating comfort noise. Pro tip from the manpage: the delay should be configured to be higher than the expected incoming jitter ๐Ÿซถ

  • --max-dtx=INT โ€” maximum duration for DTX handling, in seconds โณ

If no further RTP media is received within that time frame, DTX processing stops. You can set it to zero or negative to disable that limit and keep DTX processing on indefinitely. Defaults to 30 seconds.

  • --dtx-buffer=INT, --dtx-lag=INT โ€” bestie these two are a duo, you set them together and they cap how much stuff is allowed to pile up in the DTX buffer ๐Ÿ’–. --dtx-buffer limits the number of packets chillin' in the DTX buffer, and --dtx-lag limits the amount of audio (in milliseconds, duh) held in there. A DTX buffer overflow gets declared when both limits are blown past, and when that happens DTX processing gets sped up by dtx-shift milliseconds ๐Ÿƒโ€โ™€๏ธ๐Ÿ’จ. The defaults are 10 packets and 100 milliseconds.
  • --dtx-shift=INT โ€” how many milliseconds DTX processing gets shifted forward (sped up โฉ) or backwards (delayed โช) when there's a DTX buffer overflow or underflow. An underflow = RTP packets arriving slower than expected ๐Ÿข, and an overflow = packets arriving faster than expected ๐Ÿ‡. If you set this value to zero then no adjustments of the DTX timer happen at all โ€” instead, to keep up with the flow of received RTP packets, packets get dropped or extra DTX audio gets generated as needed ๐Ÿ˜ค.
  • --dtx-cn-params=INT โ€” specify one comfort noise parameter โœจ. This option follows the exact same format as cn-payload described below, okurrr. It applies to audio generated to fill in transmission gaps during a DTX event. Default setting is no value, which means silence gets generated to fill in those DTX gaps ๐Ÿคซ. If any CN parameters are configured, they get passed to an RFC 3389 CN decoder, and the generated comfort noise is used to fill in the DTX gaps ๐ŸŽ€.
  • --amr-dtx=native|CN โ€” pick the DTX behaviour for AMR codecs ๐ŸŽง. The default is to use the codec's internal processing: during a DTX event, a "no data" frame gets passed to the decoder and the output is used as audio data. If you pick CN here, AMR uses the same DTX mechanism as every other codec โ€” filling DTX gaps with either silence or RFC 3389 comfort noise (peep dtx-cn-params ๐Ÿ‘€). This also changes how received SID frames are processed: SID frames would not be passed to the codec, and instead get replaced by generated silence or comfort noise ๐Ÿ’….
  • --silence-detect=FLOAT โ€” turn on silence detection and set the threshold as a percent ๐Ÿ”‡. Only applies to transcoded streams, and defaults to zero (aka disabled, she's off duty). When enabled, silence detection runs on all transcoded audio streams. The threshold is basically the sensitivity dial: higher thresholds = more audio flagged as silence, lower thresholds = less audio flagged as silence. It's a percent between zero and 100. Set it to 100 and literally all audio gets detected as silence; set it to 50 and any audio quieter than 50% of the maximum volume gets flagged as silence; and so on, you get the vibe ๐ŸŒธ. Setting it to zero disables silence detection. If you only wanna catch silence that's very near or exactly absolute silence, set this to a low number like 0.01. (For certain codecs such as PCMA, a higher minimum threshold is needed to detect complete silence, because their compressed payloads don't decode to actual silence โ€” they decode to a residual DC offset instead ๐Ÿ˜ญ. For PCMA the minimum value is 0.013.) Audio detected as silence gets replaced by comfort noise as specified by the cn-payload option (see below โฌ‡๏ธ). Currently this only works for RTP peers that have advertised support for the CN RTP payload type, in which case those silence audio frames get swapped out for CN RTP frames ๐Ÿฆ‹.
  • --cn-payload=INT โ€” specify one comfort noise parameter ๐Ÿ’œ. This one can be given multiple times and the format follows RFC 3389. On the command line you list the --cn-payload= option multiple times, each one specifying a single CN parameter. In the config file though, you list the option only a single time and separate multiple CN parameters with semicolons (e.g. cn-payload = 20;40;60) ๐Ÿ“. The first CN payload value is the noise level, specified as -dBov per RFC 3389 โ€” so a noise level of zero means maximum volume ๐Ÿ”Š, and higher numbers mean lower volumes ๐Ÿค. The highest allowable number is 127, corresponding to -127 dBov, which is near silence ๐Ÿค. Subsequent CN payload values carry spectral information (reflection coefficients) per RFC 3389. Allowable values for each coefficient are between 0 and 254. Specifying spectral info is optional and the number of coefficients listed (the model order) is variable ๐ŸŽ›๏ธ. This option only applies to CN packets generated from the silence detection mechanism described above. The configured CN parameters get used directly as the payload of CN packets sent by rtpengine โœ‰๏ธ. Defaults are 32 (-32 dBov) for the noise level and no spectral information.
  • --poller-per-thread โ€” turns on the 'poller per thread' functionality โœจ: for every worker thread (check the --num-threads option) a poller gets created. With this on, it's guaranteed that only a single thread will ever read from a particular socket, which keeps the order of the packets intact ๐Ÿ”’. Might help you out when you're having issues with DTMF packets (RFC 2833) ๐Ÿ‘‘.
  • --dtls-cert-cipher=prime256v1|RSA โ€” pick the type of key used for the signature on the self-signed certificate used for DTLS ๐Ÿ”. The previous default was RSA. The current default โ€” and the only other option โ€” is prime256v1, which is a 256-bit elliptic-curve key ๐ŸŒˆ.
  • --dtls-signature=SHA-256|SHA-1 โ€” pick the hash algorithm used for the signature on the self-signed certificate used for DTLS ๐Ÿ”. Default is SHA-256. Do not confuse this with the hash algorithm used for the certificate fingerprint that gets inserted into the SDP (a=fingerprint:), because that one is independent of the certificate's signature and can be selected during runtime โš ๏ธ.
  • --dtls-rsa-key-size=INT โ€” size in bits of the RSA key used by the DTLS certificate, if RSA is in use ๐Ÿ—๏ธ. Default is 2048 bits.
  • --dtls-ciphers=STRING โ€” the ciphers allowed during the DTLS key exchange (and no, not to be confused with the cipher used by the DTLS certificate, bestie ๐Ÿ˜Œ). The format of this string is an OpenSSL cipher list. The default is DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK ๐Ÿ”ฅ.
  • --dtls-mtu=INT โ€” set the DTLS MTU to enable fragmenting of large DTLS packets ๐Ÿ“ฆ. Defaults to 1200. Minimum value is 576, because the internet protocol requires that hosts must be able to process IP datagrams of at least 576 bytes (for IPv4) or 1280 bytes (for IPv6) ๐Ÿง. This does not preclude link layers with an MTU smaller than this minimum MTU from conveying IP data. Internet IPv4 path MTU is 68 bytes.
  • --mqtt-host=HOST|IP โ€” host or IP address of the Mosquitto broker to connect to ๐Ÿ“ก. Must be set to enable exporting stats to Mosquitto.
  • --mqtt-port=INT โ€” port of the Mosquitto broker. Defaults to 1883.
  • --mqtt-id=STRING โ€” client ID to use for Mosquitto ๐Ÿท๏ธ. Default is a generated random string ๐ŸŽฒ.
  • --mqtt-keepalive=INT โ€” keepalive interval in seconds โฑ๏ธ. Defaults to 30.
  • --mqtt-user=USERNAME, --mqtt-pass=PASSWORD โ€” credentials for connecting to the Mosquitto broker ๐Ÿ”‘. At least a username must be given to enable authentication.
  • --mqtt-cafile=FILE, --mqtt-capath=PATH, --mqtt-certfile=FILE, --mqtt-keyfile=FILE โ€” enable TLS to connect to the Mosquitto broker, optionally with client certificate authentication ๐Ÿ”’โœจ. At least cafile or capath must be given to enable TLS. To enable client certificate authentication, both certfile and keyfile must be set. All files must be in PEM format. Password-protected files are not supported ๐Ÿšซ (keep those secrets unlocked, bestie).
  • --mqtt-publish-qos=0|1|2 โ€” QoS value to use for publishing to Mosquitto ๐Ÿ“ฎ. See the Mosquitto docs for the details, iykyk.
  • --mqtt-publish-topic=STRING โ€” topic string to use for publishing to Mosquitto ๐Ÿ“ข. Must be set to a non-empty string, no blank slates allowed ๐Ÿ’….
  • --mqtt-publish-interval=MILLISECONDS โ€” interval in milliseconds to publish to Mosquitto โณ. Defaults to 5000 (that's 5 seconds, cutie).
  • --mqtt-publish-scope=global|call|media โ€” set to global and one message gets published to Mosquitto every interval milliseconds containing global stats plus a list of all running calls with stats for each call ๐ŸŒ. Set to call and one message per call gets published with stats for that call every interval milliseconds, plus one message every interval milliseconds with global stats ๐Ÿ“ž. Set to media and one message per call media (usually one media per call participant, so usually 2 media per call) gets published with stats for that call media every interval milliseconds, plus one message every interval milliseconds with global stats ๐ŸŽฅ.
  • --mos=CQ|LQ โ€” the MOS calculation formula ๐Ÿ“Š. Defaults to CQ (conversational quality) which takes RTT into account and therefore requires peers to correctly send RTCP. If set to LQ (listening quality) then RTT is ignored, which lets a MOS be calculated even when there's no RTCP ๐Ÿฆ‹.
  • --socket-cpu-affinity=INT โ€” enables setting the socket CPU affinity via the SO_INCOMING_CPU socket option, if it's available ๐Ÿง โš™๏ธ. The default value is zero, which disables this feature. If set to a positive number then the CPU affinity for all sockets belonging to the same call gets set to the same value. The number specifies the upper limit of the affinity to be set, and the values get used in a round-robin fashion (e.g. if set to 8 then the values 0 through 7 get used to set the affinity ๐Ÿ”„). If this option is set to a negative number, then the number of available CPU cores gets used ๐Ÿ‘‘.

๐ŸŒธ INTERFACES ๐ŸŒธ

ok so bestie, the -i / --interface command-line option (or, same energy, the interface config file option) is how you tell RTP where your local network interfaces live. You need at least one โ€” that's not optional, babes โ€” but you can totally stack multiples. The value format is:

[NAME/]IP[!IP]

and IP can be any of these divas:

  • an IPv4 address
  • an IPv6 address
  • the name of a system network interface (like eth0)
  • a DNS host name (like test.example.com)
  • or the literal word any

Now don't get it twisted ๐Ÿ’… โ€” configuring a network interface by name is a whole different thing from the logical interface name that rtpengine uses internally (more on that below). In the [NAME/]IP[!IP] syntax, NAME is the internal logical interface name. The system network interface name goes where the first IP token goes. So for example, if you want a logical interface called int using all the addresses off your existing eth0, you write:

int/eth0

(and unless you leave it out, that second IP token โ€” the advertised address โ€” has to be a real network address, it cannot be an interface name, no exceptions bestie.)

If you use DNS host names instead of addresses or interface names, the lookup happens only once during daemon start-up. One and done. โœจ

The special keyword any means "listen on literally every available local interface address except loopback devices." Lowkey iconic. But you should only give any once, in place of a more explicit interface configuration. Don't be greedy.

To configure multiple interfaces from the command line, just drop multiple -i or --interface options. But when you're in the config file? Only use a single interface line, and separate the values with semicolons, like:

interface = internal/12.23.34.45;external/23.34.45.54

Here's where it gets spicy ๐ŸŒถ๏ธ โ€” if you give an interface option using a system interface name instead of a network address, and that system interface has multiple network addresses on it, rtpengine acts like you handed it multiple --interface options. Example time: say eth0 exists with both 192.168.1.120 and 2001:db8:85a3::7334 configured on it, and you run --interface=ext/eth0. Then rtpengine behaves exactly as if you'd given both --interface=ext/192.168.1.120 and --interface=ext/2001:db8:85a3::7334. It's giving "two for one." ๐Ÿซถ

That second IP address, the one after the exclamation point, is optional โ€” and it's for when the address you want to advertise in outgoing SDP bodies should be different from your actual local address. Super useful in certain situations, like when your SIP proxy is chilling behind NAT. For example:

--interface=10.65.76.2!192.0.2.4

means 10.65.76.2 is the real local address on the server, but outgoing SDP bodies should advertise 192.0.2.4 as the address endpoints should talk to. โš ๏ธ Heads up though: you may have to escape that exclamation point from your shell when using command-line options, like \!.

Naming an interface (slash-separated from the address) is optional. If you skip it, the name default is used. Names are the move when you want to create logical interfaces made of one or more local addresses. That way you can tell rtpengine to use particular interfaces while processing an SDP message, i.e. use different local addresses when talking to different endpoints. The most common use case? Bridging between one or more private IP networks and the public internet. Classic.

Say, for example, clients from a private IP network need to communicate their RTP with the local address 10.35.2.75, while clients from the public internet need to hit your other local address 192.0.2.67. You'd make one logical interface pub and a second one priv:

--interface=pub/192.0.2.67 --interface=priv/10.35.2.75

Then you use the direction option to tell rtpengine which local address to use for which endpoints (either pub or priv). Chef's kiss ๐Ÿ‘‘

If multiple logical interfaces are configured but the direction option isn't given in a particular call, then the first interface given on the command line gets used. First come, first served, periodt.

You can also specify multiple addresses for the same logical interface (same name). Most commonly that's one IPv4 address and one IPv6 address, like:

--interface=192.168.63.1 --interface=fe80::800:27ff:fe00:0

In that example no interface name is given, so both addresses get added to a logical interface named default. You'd then use the address family option to tell rtpengine which address to use in a particular case. ๐ŸŒˆ

And yes, you can have multiple addresses of the same family on one logical network interface. In that case, the first address (of a particular family) given for an interface becomes the primary address rtpengine uses for most purposes. Any extra addresses get advertised as additional ICE candidates with increasingly lower priority. This is great on multi-homed systems and lets endpoints pick the best possible path to reach the RTP proxy. If ICE isn't being used, those extra addresses just go unused โ€” even though ports would still get allocated on those interfaces. ๐Ÿ’€ (wasteful but valid)

Another option: give interface names in the format BASE:SUFFIX. This lets interfaces be used in a round-robin fashion, which is useful for load-balancing the port ranges of multiple interfaces. Consider this config:

--interface=pub:1/192.0.2.67 --interface=pub:2/10.35.2.75

These two interfaces can still be referenced directly by name (e.g. direction=pub:1), but now you can also reference just the base name (direction=pub). If you use the base name, one of the two interfaces is selected round-robin style โ€” and only if that interface actually has enough open ports available. This effectively increases the number of available media ports across multiple IP addresses. There's no limit on how many interfaces can share the same base name. Go off. ๐Ÿ”ฅ

You can combine BASE:SUFFIX notation with multiple addresses for the same interface name too. Here's an advanced example (config file notation, actual network addresses omitted):

interface = pub:1/IPv4 pub:1/IPv4 pub:1/IPv6 pub:2/IPv4 pub:2/IPv6 pub:3/IPv6 pub:4/IPv4

In this example, when direction=pub and IPv4 is needed as a primary address, either pub:1, pub:2, or pub:4 might be selected. When pub:1 is selected, one IPv4 and one IPv6 address will be used as additional ICE alternatives. For pub:2, only one IPv6 is used as an ICE alternative, and for pub:4 no alternatives would be used. When IPv6 is needed as a primary address, either pub:1, pub:2, or pub:3 might be selected. And if at any given time not enough ports are available on any interface, it won't be selected by the round-robin algorithm. No ports, no party. ๐Ÿ™ˆ

You can use the round-robin algorithm even when direction isn't given. If the first given interface has the BASE:SUFFIX format, then the round-robin algorithm kicks in and selects interfaces with the same BASE name. โœจ

One last thing, and this one's important: if you're not using the NG protocol but rather the legacy UDP protocol used by the rtpproxy module, then the interfaces must be named internal and external, corresponding to the i and e flags, if you want network bridging in this mode. Old school rules, bestie.


๐ŸŽ€ EXIT STATUS ๐ŸŽ€

  • 0 โ€” Successful termination. We love a happy ending. ๐Ÿ’–
  • 1 โ€” An error occurred. ๐Ÿ˜ญ

๐Ÿงš ENVIRONMENT ๐Ÿงš

  • RTPENGINE_REDIS_AUTH_PW โ€” Redis server password for persistent state storage.
  • RTPENGINE_REDIS_WRITE_AUTH_PW โ€” Redis server password for write operations, if --redis has been specified, in which case the one specified in --redis will be used for read operations only.

๐Ÿฆ‹ FILES ๐Ÿฆ‹

  • /etc/rtpengine/rtpengine.conf โ€” Configuration file.

๐Ÿ’œ EXAMPLES ๐Ÿ’œ

A typical command line (enabling both UDP and NG protocols) may look like:

rtpengine --table=0 --interface=10.64.73.31 --interface=2001:db8::4f3:3d \
  --listen-udp=127.0.0.1:22222 --listen-ng=127.0.0.1:2223 --tos=184 \
  --pidfile=/run/rtpengine.pid

ok that's the whole vibe. copy it, run it, slay. ๐Ÿ’…


โญ SEE ALSO โญ


and that's a wrap on the whole page, bestie!! ๐Ÿซถ this absolute icon of a manual page comes to you as version 10.5.3.5-1+deb12u1, dated 2026-07-20, and it's the RTPENGINE(1) page โ€” go forth and proxy some RTP, you've got this, mwah ๐Ÿ’–๐ŸŽ€

๐Ÿ“ฆ where this page comes from