rtpengine(8)
๐ rtpengine(8) โ NGCP proxy for RTP and other UDP based media traffic ๐
โจ NAME โจ
rtpengine โ NGCP proxy for RTP and other UDP based media traffic
๐ SYNOPSIS ๐
rtpengine --interface=addr...
--listen-tcp|--listen-udp|--listen-ng|--listen-tcp-ng|--listen-http|--listen-https=addr...
[option...]
๐ DESCRIPTION ๐
okay so bestie, the Sipwise NGCP rtpengine is a proxy for RTP traffic and other UDP based media traffic. It is meant to be used with the Kamailio SIP proxy and forms a drop-in replacement for any of the other available RTP and media proxies. periodt. ๐ฆ
๐ OPTIONS ๐
Most of these options are indeed optional, with two exceptions. Itโs mandatory to specify at least one local IP address through --interface, and at least one of the --listen-... options must be given. no cap. ๐
All options can (and should) be provided in a config file instead of at the command line. See the --config-file option below for details. โจ
--helpโ Print the usage information. ๐-v,--versionโ If called with this option, thertpenginedaemon will simply print its version number and exit. ๐--codecsโ Print a list of supported codecs and exit. ๐ฆ--config-file=FILEโ Specifies the location of a config file to be used. The config file is an.inistyle config file, with all command-line options listed here also being valid options in the config file. For all command-line options, the long name version instead of the single-character version (e.g.tableinstead of justt) must be used in the config file. For boolean options that are either present or not (e.g.no-fallback), a boolean value (eithertrueorfalse) must be used in the config file. If an option is given in both the config file and at the command line, the command-line value overrides the value from the config file. Options that can be specified multiple times on the command line must be given only once in the config file, with the multiple values separated by semicolons (see sectionINTERFACESbelow for an example). As a special value,nonecan be passed here to suppress loading of the default config file/etc/rtpengine/rtpengine.conf. ๐ญ--config-section=STRINGโ Specifies the.inistyle section to be used in the config file. Multiple sections can be present in the config file, but only one can be used at a time. The default value isrtpengine. A config file section is started in the config file using square brackets (e.g.[rtpengine]). ๐-t,--table=INTโ Takes an integer argument and specifies which kernel table to use for in-kernel packet forwarding. See the section on in-kernel operation in theREADME.mdfor more detail. Optional and defaults to zero. If in-kernel operation is not desired, a negative number can be specified. โจ--nftables-chain=CHAINโ Name of the netfilter chain in which to create the custom forwarding rule required for in-kernel packet forwarding. Defaults tortpengine. Only used if in-kernel packet forwarding is enabled (table set to zero or higher). At startuprtpenginecreates a new netfilter chain with this name (in thefiltertable) if it doesnโt yet exist, or flushes (empties out) the chain if it already exists. It then creates a single forwarding rule in this chain to direct media packets into the kernel module for processing. The rule and the chain are deleted during shutdown. Explicitly setting this option to an empty string disables managing of a netfilter chain and prevents creation of the custom forwarding rule. ๐--nftables-base-chain=CHAINโ Name of the netfilter base chain to use as entry point for in-kernel packet forwarding. Defaults toINPUTto match legacyiptablessetups. Only applicable if the optionnftables-chainis active. If the chain with this name doesnโt exist during startup,rtpenginewill create it as a base chain. It then adds a single immediate-goto (jump) rule to the chain given by thenftables-chainoption. During shutdown this rule is again deleted. If this option is explicitly set to an empty string, thenrtpenginewill directly create the chain given bynftables-chainas a base chain and skip creating the immediate-goto rule. If this option is set to the special stringnone, thenrtpenginewill create its custom chain and rule as it normally would, but will skip adding an immediate-goto rule to the custom chain. Doing so requires the operator to manually create this immediate-goto rule somewhere themselves. Otherwise in-kernel packet forwarding would be left inoperable. ๐--nftables-appendโ With this option set, the netfilter rule created in the base chain is appended to the list of existing rules. The default is to prepend it (insert it at the beginning). ๐--nftables-family=ip|ip6|ip,ip6|inetโ Configure for which netfilter address family to manage tables, chains, and rules. The default is to manage both IPv4 and IPv6 address families. ๐--xtablesโ Manage a netfilter rule pointing to the legacy x-tables target (xt_RTPENGINE) instead of the more modern nftablesrtpengineexpression. โจ--nftables-start--nftables-stopโ Instructsrtpengineto execute the actions described undernftables-chainandnftables-base-chainand then immediately exit. Useful to manually re-create the rule(s) if they have gotten lost during runtime, and/or to manually manage creation and deletion of these rules from a script (typically in combination with an emptynftables-chain=in the main config file). This applies to both--nftables-startand--nftables-stop. ๐ฅ--nftables-statusโ Instructsrtpengineto check for the existence of the managed netfilter rules and chains, print the result of check, and exit. The process will exit with code0if the check was successful, and1otherwise. ๐-F,--no-fallbackโ Will prevent fallback to userspace-only operation if the kernel module is unavailable. In this case, startup of the daemon will fail with an error if this option is given. ๐--templates=STRโ Name of the config file section to contain signalling templates. Requires a configuration file to be in use (i.e. not--config-file=none). Default value is unset (i.e. no templates supported). If set, then each entry within the given config section corresponds to a named signalling template, which can then be used by referencing it via thetemplate=...key in a signalling message tortpengine. See sectionSIGNALLING TEMPLATESbelow. ๐ฆ-S,--save-interface-portsโ Will bind ports only on the first available local interface, of desired family, of logical interface. If no ports available on any local interface of desired family, give an error message. In this case, ICE will be broken. ๐ญ-i,--interface=[NAME/]IP[!IP]โ Specifies a local network interface for RTP. At least one must be given, but multiple can be specified. See the sectionINTERFACESjust below for details. ๐-l,--listen-tcp=[IP:]PORT-u,--listen-udp=[IP46:]PORT-n,--listen-ng=[IP46:]PORT-n,--listen-tcp-ng=[IP46:]PORT
These options each enable one of the 4 available control protocols if given and each take either just a port number as argument, or an address:port pair, separated by 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 is provided for backwards compatibility.
The udp protocol is used by Kamailioโs rtpproxy module. 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 can be used with Kamailioโs rtpengine module. With this protocol, the complete SDP body is passed to rtpengine, rewritten and passed back to Kamailio. Several additional features are available with this protocol, such as ICE handling, SRTP bridging, etc.
The tcp-ng protocol is in fact the ng protocol but transported over TCP.
It is recommended to specify not only a local port number, but also 127.0.0.1 as interface to bind to.
Each option can be given multiple times to open multiple control ports of the same type. In the config file, the option can be given only once, with multiple addresses and ports separated by semicolons.
-c,--listen-cli=[IP46:]PORTโ TCP IP and port to listen for the CLI (command line interface). This option can be given multiple times to open multiple CLI ports. In the config file, the option can be given only once, with multiple addresses and ports separated by semicolons. ๐-g,--graphite=IP46:PORTโ Address of the graphite statistics server. โจ-w,--graphite-interval=INTโ Interval of the time when information is sent to the graphite server. ๐--graphite-prefix=STRINGโ Add a prefix for every graphite line. ๐ฆ--graphite-timeout=INTโ Sets after how much time (seconds) to force fail graphite socket connection, when graphite server is filtered out. If set to0, there are no changes. ๐-t,--tos=INTโ Takes an integer as argument and if given, specifies the TOS value that should be set in outgoing packets. The default is to leave the TOS field untouched. A typical value is184(Expedited Forwarding). ๐ฅ--control-tos=INTโ Takes an integer as argument and if given, specifies the TOS value that should be set in the control-ng interface packets. The default is to leave the TOS field untouched. This parameter can also be set or listed viartpengine-ctl. ๐--control-pmtu=want|dontโ Forces a specific PMTU discovery behaviour on IPv4 UDP control sockets, overriding the system-wide default. If set towantthen path MTU discovery is performed, initially enabling the DF (donโt fragment) bit on outgoing IPv4 packets until the path MTU has been discovered through reception of a โfragmentation neededโ ICMP packet. If set todontthen path MTU discovery is disabled, leaving the DF bit unset, and relying on the routers within the network path to perform any necessary fragmentation. The setting ofdontis useful in broken IPv4 environments without functioning PMTU discovery, for example in networks which unconditionally block all ICMP. ๐-o,--timeout=SECSโ Takes the number of seconds as argument after which a media stream should be considered dead if no media traffic has been received. If all media streams belonging to a particular call go dead, then the call is removed fromrtpengineโs internal state table. Defaults to60seconds. ๐-s,--silent-timeout=SECS
Ditto as the --timeout option, except this one's for muted or inactive media streams. Defaults to 3600 (one hour). ๐ค
-a,--final-timeout=SECSโ how many seconds since the call was created that we wait, after the call gets deleted. Super handy for capping how long a call can possibly live. Wanna turn it off? Set the parameter to0. By default this timeout is disabled, periodt.--offer-timeout=SECSโ this timeout (in seconds) applies to calls that only ever had an offer and never got an answer. ๐ญ Defaults to3600(one hour).-p,--pidfile=FILEโ tells rtpengine the path and file name to write the daemon's PID number to.-f,--foregroundโ if you pass this, the daemon will not daemonize, meaning it stays in the foreground. Iconic for debugging. ๐-m,--port-min=INTโ takes an integer, and together with--port-maxdefines the local port range that rtpengine allocates UDP ports from for media traffic relay. Defaults to30000.-M,--port-max=INTโ the other half of that port range, also an integer. Defaults to40000.
So like, quick recap: --port-min defaults to 30000, --port-max defaults to 40000, and together they're your range.
If you've got multiple interfaces configured and you're using the new configuration-file based syntax, then different port ranges can be configured for different interfaces (see below). โจ
-L,--log-level=INTโ takes an integer and controls the highest log level that gets sent to syslog. Ngl this is merely the default log level used for logging subsystems (see below) that don't explicitly have their own separate log level configured.
The log levels line up with the ones in the syslog(3) man page. Default is 6, which is LOG_INFO. Highest possible value is 7 (LOG_DEBUG), which logs everything. ๐
At runtime you can decrease the log level by sending SIGURS1 to the daemon, and increase it with SIGUSR2. Yes bestie, that's the exact spelling in here, we keep it fr fr.
--log-level-subsystem=INTโ configures a log level for one of the logging subsystems. A subsystem that doesn't have a level explicitly configured inherits its default from thelog-levelsetting above โ with the exception of theinternalssubsystem, which by default has all logging disabled. ๐
You can see the full list of logging subsystems by pulling up the --help online help. Some (if not all) of them 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), dtx.
--log-facilty=daemon|local0|...|local7|...โ the syslog facilty to use when sending log messages to the syslog daemon. Defaults todaemon. (And yes, that's genuinely how it's spelled here. ๐ )--log-facilty-cdr=daemon|local0|...|local7|...โ same as--log-facility, except the difference is that only CDRs get written to this log facility.--log-facilty-rtcp=daemon|local0|...|local7|...โ same as--log-facility, except only RTCP data gets written here. โ ๏ธ Be careful with this one, because there may be a lot of information written to it.--log-facilty-dtmf=daemon|local0|...|local7|...โ same as--log-facility, except only DTMF events get written here. Those DTMF events are extracted from RTP packets conforming to RFC 4733, encoded in JSON format, and written as soon as the end of an event is detected. ๐--log-format=default|parsableโ picks between multiple log output styles. The default prefixes log lines with a description of the relevant entity, like[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โ].--dtmf-log-dest=IP46:PORTโ configures a target address for logging detected DTMF events. Similar vibe 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-ngis enabled, this sends DTMF events to all connected clients encoded in bencode format.--dtmf-no-log-injectsโ if--dtmf-no-log-injectsis enabled, then DTMF events resulting from a call to inject-DTMF won't be sent to--dtmf-log-dest=or--listen-tcp-ng.--dtmf-no-suppressโ some RTP clients keep sending audio RTP packets during a DTMF event, which means both audio packets and DTMF packets show 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.--log-srtp-keysโ write SRTP keys to the error log instead of the debug log. ๐-E,--log-stderrโ log to stderr instead of syslog. Only useful in combination with--foreground.--split-logsโ split multi-line log messages into individual log messages, so every single line gets its own log line prefix.--max-log-line-length=INTโ split log lines into multiple lines once they exceed the character count you give here. You can set it to a negative value to allow unlimited-length log lines. Set it to zero for the default value, which is unlimited if logging to stderr, or500if logging to syslog.--no-log-timestampsโ don't add timestamps to log lines written to stderr. Only useful in combination with--log-stderr.--log-name=STRINGโ sets the id that gets printed in syslog. Defaults tortpengine.--log-mark-prefix=STRINGโ a prefix added to particular data fields in log files that are deemed sensitive and/or private information. Defaults to an empty string.--log-mark-suffix=STRINGโ a suffix added to particular data fields in log files that are deemed sensitive and/or private information. Defaults to an empty string. ๐คซ--num-threads=INTโ how many worker threads to create, and it must be at least one. The default is to create as many threads as there are CPU cores available. 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 asnum-threads. You can set it to zero if you don't want any media playback functionality at all. ๐ โโ๏ธ
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 for example, if this option is set to 4, in total 8 threads will be launched.
--codec-num-threads=INTโ enables asynchroneous transcoding operation using the specified number of worker threads. Heads up though: this is an experimental feature and probably doesn't bring any benefits over normal synchroneous transcoding. ๐งช--poller-size=INTโ sets the maximum number of event items (file descriptors) to retrieve from the underlying system poll mechanism per iteration. Defaults to128. A lower number can lead to improved load-balancing among a large number of threads.--thread-stack=INTโ sets the stack size of each thread to the value given in kB. Defaults to2048kB. Can be set to-1to leave the default provided by the OS unchanged.--evs-lib-path=FILEโ points to the shared object file (.so) containing the reference implementation for the EVS codec. See theREADMEfor more details.--sip-sourceโ okay so, the original rtpproxy as well as 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. ๐ฐ๏ธ--dtls-passiveโ enables theDTLS=passiveflag for all calls, unconditionally.-d,--delete-delay=INTโ delete the call from memory after the specified delay. Can be set to zero for immediate call deletion.-r,--redis=[PW@]IP:PORT/INTโ connect to the specified Redis database (with the given database number) and use it for persistence storage. The format of this option isADDRESS:PORT/DBNUM, for example127.0.0.1:6379/12to connect to Redis DB number 12 running on localhost on the default Redis port.
If the Redis database is protected with an authentication password, you can supply that password by prefixing the argument value with the password, separated by an @ symbol, for example foobar@127.0.0.1:6379/12. โ ๏ธ But note that this 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, this time 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).
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). ๐ช
--redis-subscribe=[PW@]IP:PORT[/INT]โ configures a Redis database for subscribing and receiving notifications. This option takes precedence over--redis, if configured. When--subscribe-keyspaceis also configured, the keyspace part of--redis-subscribe=is not used โ the former takes precedence. The keyspace number can also be omitted altogether.
For password protected Redis servers, the environment variable for the password is RTPENGINE_REDIS_SUBSCRIBE_AUTH_PW.
-k,--subscribe-keyspace=INT--subscribe-keyspace=โ the actual list of redis keyspaces you wanna subscribe to. If this and--redis-subscribe=are both missing, nothing gets subscribed (that's the default vibe). And if you use it together with--redis-subscribe=, then--subscribe-keyspace=just straight-up overwrites the keyspaces to subscribe to.
Later on you can add or remove subscriptions at runtime via rtpengine-ctl ksadd / rtpengine-ctl ksrm. Doing that might flip the redis keyspace notification feature on or off, jsyk.
You're allowed to leave the subscription list empty at first โ but ๐ if you ever want to add keyspaces later during runtime, the feature still has to be configured at rtpengine startup. You do that either by setting --redis-subscribe= to a valid address, or by listing the single value -1 under --subscribe-keyspace=.
--redis-num-threads=INTโ how many redis restore threads to spin up. Default is4.--redis-expires=INTโ expire time for redis keys, in seconds. Default is86400.--active-switchoverโ turn this on and any activity on a call (think signalling, media, whatever) that was created through a Redis keyspace notification makes rtpengine take control of that call. Without it, rtpengine needs an explicit command to take control of a call (or to give it up). No autopilot, bestie.-q,--no-redis-requiredโ with this flag present (orNO*REDIS*REQUIRED=yes'or1in the config file), rtpengine will start even when there's no initial connection to the redis databases โ whether that's-r,-w`, or both of them.
Heads up though: if the -r redis can't be connected to initially, sessions do not get reloaded when rtpengine starts. rtpengine still starts, but yeah. Worth knowing. ๐ซถ
--redis-allowed-errorsโ if this is present and its value is>= 0, it configures how many consecutive errors are allowed while talking to a redis server before redis communication gets temporarily disabled for that server. While it's disabled, there are no reconnect attempts and no commands sent to that server. Default value is-1, which means the whole feature is off. You can also set or list this parameter viartpengine-ctl.--redis-disable-timeโ how many seconds redis communication stays disabled because of errors. It works in tandem with theredis-allowed-errorsparameter. Default is10. Also settable / listable viartpengine-ctl.--redis-cmd-timeout=INTโ set this to a non-zero value and it becomes the timeout, in milliseconds, for each command sent to the redis server. If redis doesn't reply in time, that command just fails. Default is0, meaning commands block with no timeout at all. Settable / listable viartpengine-ctltoo โ but note that setting this parameter to0requires a reconnect on all configured redis servers. ๐ญ--redis-connect-timeout=INTโ the timeout value, in milliseconds, for connecting to a redis server. If it can't connect within that window, the connection fails. Important detail: on failure, when reconnecting to redis, aPINGcommand is issued before attempting to connect, so the--redis-cmd-timeoutvalue gets added onto the total waiting time. Super useful if you're using--redis-allowed-errorsand trying to estimate the total lost time when redis takes a lil nap. Default connection timeout is1000ms. Also settable / listable viartpengine-ctl.--redis-resolve-on-reconnectโ enables theredis resolve on reconnectfunctionality: when re-connecting to the remote redis server, it tries to re-resolve if the redis hostname was an FQDN and not an IP address. It's a boolean value (eithertrueorfalse).--redis-format=bencode|JSONโ picks the format for serialised call data written to Redis or KeyDB. The old default (and honestly the only option before) was a JSON object. The new default is bencode formatting. Bencode lowkey slays performance-wise โ better speed, lower CPU usage โ at the cost of being way less human readable. ๐
Both formats can be restored from, no matter what this setting is. So you're not locked in.
--redis-tcp-keepalive-time=INT--redis-tcp-keepalive-intvl=INT--redis-tcp-keepalive-probes=INT
These three control TCP keepalive behaviour on connections to Redis. The time value defaults to zero, and it must be set to a non-zero positive value to actually enable keepalives โ it's the number of seconds a socket has to sit idle before keepalives start getting sent.
intvl is the time in seconds between keepalives, and probes is how many keepalive probes get sent before the connection is deemed dead. Defaults are 1 and 3 respectively.
-b,--b2b-url=STRINGโ enables and sets the URI for an XMLRPC callback that fires when a call is torn down due to packet timeout. The special code%%can stand in for an IP address, and in that case the source address of the originating request gets used (or alternatively the address set with thexmlrpc-callbackng protocol option).-x,--xmlrpc-format=INTโ selects the internal format of the XMLRPC callback message for B2BUA call teardown.0is for SEMS,1is a generic format that only contains the call-ID,2is for Kamailio.--max-sessions=INTโ caps the number of maximum concurrent sessions. Set it at startup viamax-sessionsin the config file. Set it at runtime via thertpengine-ctlutil. And listen โrtpengine-ctl set maxsessions 0can be used for draining rtpengine sessions. The whole flow:- Enable feature:
max-sessions=1000 - Enable feature:
rtpengine-ctl set maxsessions >= 0 - Disable feature:
rtpengine-ctl set maxsessions -1
- Enable feature:
By default the feature is disabled, i.e. maxsessions == -1.
--max-load=FLOATโ if the current 1-minute load average goes over the value you put here, new sessions get rejected until the load average drops back under the threshold. Vibe check for your box. ๐--max-cpu=FLOATโ if current CPU usage (in percent) goes over the value you put here, new sessions get rejected until CPU usage drops back under the threshold. CPU usage is sampled every0.5seconds. Only supported on systems that provide a Linux-style/proc/stat. So like, Linux things only, bestie.--max-bandwidth=INTโ if current bandwidth usage (in bytes per second) goes over the value you put here, new sessions get rejected until bandwidth usage drops back under the threshold. Bandwidth usage is sampled every1second and is based on received packets, not sent ones. Important distinction, don't forget it.--max-recv-iters=INTโ sets the maximum continuous reading cycles in the UDP poller loop. This can help dodge dropped-packet errors on bursty streams. Default is50.--homer=IP46:PORTโ enables sending the decoded contents of RTCP packets to a Homer SIP capture server. Transport is HEP version 3 and the payload format is JSON. This argument takes an IP address and a port number as its value. It also enables sending the control NG traffic to a capturing agent โ but payload format does not apply in that case.--homer-protocol=udp|tcpโ can be eitherudportcp, withudpbeing the default.--homer-id=INTโ the HEP protocol Homer uses contains a โcapture IDโ to tell different sources of capture data apart. You specify that ID with this argument.--homer-disable-rtcp-statsโ kills the default behaviour where RTCP stats get sent when thehomerparameter is set. This way, sending of RTCP and NG are decoupled from each other.--homer-enable-ngโ enables sending control NG packages to Homer capturing software. The capturing agent part is not officially supported OOTB, but you can make it happen with Kamailio by using the config. For this feature to work you have to set at least thehomerparameter.--homer-ng-capture-proto=INTโ the HEP protocol Homer uses contains a โCapture protocol typeโUINT8that the capturing agent and UI use to do further processing. Some values are registered, but currently0x3dvalues onwards are free. Default value is0x3d(61).--recording-dir=FILEโ an optional argument that specifies a path to a directory where PCAP recording files and recording metadata files should be stored. If you don't specify it, support for call recording is disabled. Period.
rtpengine supports multiple mechanisms for recording calls โ peep recording-method below for the list. The default recording method pcap is the one described in this section.
PCAP files get stored inside a pcap subdirectory, and metadata inside 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
There are two empty lines between each logical block of metadata. All answer SDP gets written out, each one separated from the others by one empty line. That generic metadata at the end can be any length, with any number of lines. Metadata files show up in the subdirectory once the call completes. PCAP files get written to the subdirectory as the call is being recorded โ live, bestie. ๐
Because call recording via this method happens entirely in userspace, in-kernel packet forwarding cannot be used for calls that are currently being recorded. Packet forwarding will thus be done in userspace only. No kernel shortcuts for these calls, sorry.
--recording-method=pcap|proc|allโ multiple methods of call recording are supported, and this option picks one. Currently supported:pcap,proc, andall. The default method ispcap, and it's the one described above.
The proc recording method works by writing metadata files directly into the recording-dir (i.e. not into a subdirectory), and instead of recording RTP packet data into pcap files, the packet data is exposed via a special interface in the /proc filesystem. Packets then have to be retrieved from that interface by a dedicated userspace component (usually a daemon, like the recording-daemon included in this repository).
So packet data just vibes in kernel memory until the userspace component comes and retrieves it ๐พ but heads up: only a limited number of packets (default 10) per media stream get held. If they're not retrieved in time, they simply get discarded โ no drama, just gone. The whole point of this is that you can flag every call to be recorded, and then it's up to the userspace component to decide whether it actually wants to use that packet data for anything or not. Main character energy, honestly โจ
And bestie, in-kernel packet forwarding is fully supported with this recording method, even for calls that are being recorded. It's giving "you can absolutely have both" ๐
Oh also โ the recording method all turns on both pcap and proc at the same time. Slay.
--recording-format=raw|ethโ when you're recording to a pcap file inraw(the default), there is no ethernet header. Set it toethinstead and a fake ethernet header gets added, which makes every package a whole14bytes bigger. ๐--record-egressโ apply media recording to egress media streams (as they're sent out by rtpengine) instead of to media streams as they're received. This is what lets you capture manipulated and generated media (like, say, stuff from the play media command) inside your recordings. ๐ฅ--iptables-chain=STRINGโ this one enables explicit management of an iptables chain. When you turn it on, rtpengine takes control of the given iptables chain, and that chain must already exist before you start the daemon, okay? On startup, rtpengine flushes the chain, then adds oneACCEPTrule for each media port (RTP/RTCP) opened. Each rule matches the individual port and destination IP address exactly, and gets created with the call ID as an iptables comment. The rule gets deleted when the port is closed. Iconic bookkeeping ๐
This option is what lets you build a firewall with a default DROP policy across the entire port range rtpengine uses, and then reference that iptables chain so you only selectively allow the ports actually in use.
Note: this applies only to media ports โ it does not apply to any other ports (like the control ports) that rtpengine uses.
Also a reality check: the iptables API is not the most efficient one out there and it does not lend itself to fast dynamic creation and deletion of rules. If you've got a high call volume, especially tons of call attempts per second, you might hit a significant performance impact. This is not rtpengine's fault, bestie โ it's iptables and its API implementation in the Linux kernel. In that case, the recommended move is to add a static iptables rule covering the whole media port range instead, and just not use this option.
--scheduling=default|...--priority=INT--idle-scheduling=default|...--idle-priority=INT
These four are the thread-scheduling knobs ๐ธ The scheduling and priority settings apply to the main worker threads, while the idle- versions of those same settings apply to the 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 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, so refer to 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 the other scheduling policies (including when no policy is specified), the priority settings correspond to the nice value and should be in the range of -20 (high) through 19 (low). Heads up: not all systems support thread-specific nice values, and on such a system, using these settings might have unexpected results. (Linux does support thread-specific nice values, so you're good there.) Refer to your system's sched(7) man page.
--mysql-host=HOST|IP--mysql-port=INT--mysql-user=USERNAME--mysql-pass=PASSWORD
This is the configuration for playing back media files that live in a MySQL (or MariaDB) database ๐ At minimum, mysql-host must be configured for any of this to work. The others are optional and default to their respective values from the MySQL/MariaDB client library.
--mysql-query=STRINGโ the query used for retrieving media files from the database. No default exists, so this is a mandatory configuration for media playback from a database. The query string you provide must contain the single format placeholder%lluand must not contain any other format placeholders. The ID value passed to rtpengine in thedb-idkey of the play media message gets used in place of the placeholder when querying the database.
An example configuration might look like this:
mysql-query = select data from voip.files where id = %llu
--endpoint-learning=delayed|immediate|off|heuristicโ picks one of the available algorithms for learning RTP endpoint addresses ๐ฆ The legacy setting isdelayed, which waits3seconds before committing to an endpoint address, and the address is then learned from the first incoming RTP packet seen after that delay. The settingimmediatelearns the endpoint address from the first incoming packet seen, no 3-second delay. Usingoffdisables endpoint learning altogether โ which will likely break clients behind NAT, so be careful. The settingheuristicincludes that same 3-second delay, but source addresses seen from incoming RTP packets get ranked according to preference: if a packet with a source address and port matching the SDP address is seen, that address is used. Otherwise, if a packet with a matching source address (but a different port) is seen, that address is used. Otherwise, if a packet with a matching source port (but a different address) is seen, that address is used. Otherwise, the source address of any 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. Also worth knowing: the jitter buffer is currently only implemented for userspace operation.
When adaptive jitter buffer sizing is enabled (see --jb-adaptive below), this value serves as the base capacity for burst-aware packet discard, and as a fallback for when adaptive sizing isn't available yet (like during initial packet collection).
--jb-clock-driftโ enable clock drift compensation for the jitter buffer โ--jb-adaptiveโ enable adaptive jitter buffer sizing โจ When it's on, the jitter buffer size is automatically adjusted based on measured network jitter using statistical analysis. The buffer size is calculated as the mean jitter plus four standard deviations (ฮผ + 4ฯ), providing99.99%coverage of packet arrival variations.
Adaptive sizing needs at least 10 jitter samples before it can calculate the optimal buffer size, and the calculation gets updated every 10 packets. The calculated buffer size is constrained by the --jb-adaptive-min and --jb-adaptive-max limits.
Burst-aware packet discard is always active (regardless of this setting) and figures out the minimum number of packets to remove during buffer overflow based on the time elapsed since the last packet, instead of discarding all buffered packets. This typically results in an 80-90% reduction in packet loss during burst events. When adaptive sizing is enabled, burst-aware discard uses the dynamically calculated buffer size; otherwise it uses the static buffer size from --jitter-buffer.
If this option is not specified, the jitter buffer just uses a fixed size as specified by --jitter-buffer.
--jb-adaptive-min=INTโ minimum adaptive jitter buffer size in milliseconds ๐ This sets the lower bound for the automatically calculated buffer size when--jb-adaptiveis enabled. Must be greater than or equal to zero. If not specified, it defaults tozero.
This one's useful for services with strict latency requirements, making sure the adaptive buffer never shrinks below a certain threshold even in low-jitter network conditions. ๐
--jb-adaptive-max=INTโ maximum adaptive jitter buffer size in milliseconds ๐ธ This sets the upper bound for the automatically calculated buffer size when--jb-adaptiveis enabled. Must be greater than or equal to zero. If not specified when--jb-adaptiveis enabled, it defaults to300milliseconds. The maximum allowed value is1000milliseconds.
This option stops the adaptive buffer from growing excessively large in high-jitter network conditions, which could introduce unacceptable latency. ๐ญ
Watch out: if--jb-adaptive-minis greater than--jb-adaptive-max, the daemon will exit with an error during startup. No vibes, just death. ๐
--debug-srtpโ turn on extra log messages to help debug SRTP issues ๐ Per-packet details โ sequence numbers, ROC, payloads (both plain text and encrypted), authentication tags, all of it โ get recorded to the log. Every RTCP packet gets logged like this, while every512thRTP packet gets logged. This only applies to packets forwarded/processed in userspace.--reject-invalid-sdpโ with this set, refuse 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.--listen-http=[IP|HOSTNAME:]PORT--listen-https=[IP|HOSTNAME:]PORT
Enable listening for HTTP or WebSocket connections, or their TLS-secured counterparts HTTPS and WSS ๐ซถ If no interface is specified, then 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. Iconic range, honestly. ๐
If HTTPS/WSS is enabled, a certificate must also be provided 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 not specified, then the same number as given undernum-threadsgets used. And if no HTTP listeners are enabled at all, then no threads are created. Efficient! โจ--http-buf-size=INT
And this one sets the internal buffer size used by the HTTP/HTTPS/WS/WSS library, in kilobytes. If you leave it unset, or set it to something โค zero, the implementation-defined default gets used instead. Bump it to 1024 (that's one whole megabyte, bestie) or larger if your WS activity is highkey poppin' off โ it improves performance and prevents those possible protocol hiccups. ๐ฆ
--http-idle-timeout=INTโ how many seconds idle HTTP/HTTPS keepalive connections are allowed to justโฆ sit there before the server closes them. If it's zero (which is the default), the libwebsockets default is used instead (that's 5 seconds for HTTP/1.1). Set it to a positive value (like60) to align with your load balancer's idle timeouts, or just to keep those connections open longer. โจ--software-id=STRINGโ sets a free-form string used to identify this software towards external systems, for example in outgoing ICE/STUN requests. Defaults tortpengine-VERSION. The string gets sanitised โ every non-alphanumeric character is replaced with a dash โ so it's universally usable. No cap, that's the whole thing.--dtx-delay=INTโ processing delay in milliseconds for handling discontinuous transmission (DTX) or other transmission gaps. Defaults to zero, i.e. disabled, and it's applicable to transcoded audio streams only. When you turn it on, it delays processing of received packets for the specified time (very much like a jitter buffer) so that DTX handling gets triggered when a transmission gap happens. The decoder is then told to fill in the missing time during the gap โ generating comfort noise, for example. Configure the delay to be higher than the incoming jitter you expect, okay? That's the vibe.--max-dtx=INTโ maximum duration for DTX handling, in seconds. If no further RTP media comes in within this time frame, DTX processing stops. You can set it to zero or negative to disable that and keep DTX processing going indefinitely. Defaults to30seconds. โณ--dtx-buffer=INT--dtx-lag=INT
These two besties work together to control the maximum number of packets and the amount of audio that's allowed to be held in the DTX buffer. The dtx-buffer option limits the number of packets held there, while dtx-lag limits the amount of audio (in milliseconds) held there. A DTX buffer overflow is declared when both limits are exceeded, and in that case DTX processing gets sped up by dtx-shift milliseconds. ๐
The defaults are 10 packets and 100 milliseconds.
--dtx-shift=INTโ the amount of time in milliseconds that DTX processing is shifted forward (sped up) or backwards (delayed) when there's a DTX buffer overflow or underflow. An underflow happens when RTP packets arrive slower than expected; an overflow happens when they arrive faster than expected. ๐ข๐
If you set this to zero, then no adjustments of the DTX timer get made. 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 ascn-payload, described below. ๐
It's applicable to audio generated to fill in transmission gaps during a DTX event. The 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 what fills in the DTX gaps.
--amr-dtx=native|CN--evs-dtx=native|CN
Select the DTX behaviour for AMR and EVS codecs, respectively. The default is to use the codec's internal processing: during a DTX event, a "no data" frame is passed to the decoder and the output is used as the audio data.
If you pick CN here instead, AMR/EVS use the same DTX mechanism that other codecs use โ filling in DTX gaps with either silence or RFC 3389 comfort noise (peep dtx-cn-params). This also changes how received SID frames get processed: SID frames won't be passed to the codec, they'll be replaced by generated silence or comfort noise instead. ๐
--silence-detect=FLOATโ enable silence detection and specify the threshold as a percent. This option is applicable to transcoded streams only, and it defaults to zero (disabled). ๐คซ
When enabled, silence detection runs on all transcoded audio streams. The threshold you give is the sensitivity for detecting silence: higher thresholds mean more audio gets detected as silence, while lower thresholds mean less audio gets detected as silence. The threshold is a percent between zero and 100. Set it to 100 and literally all audio would be detected as silence; set it to 50 and any audio quieter than 50% of the maximum volume gets detected as silence; and so on. Setting it to zero disables silence detection. If you only want to catch silence that's very near or exactly equal to absolute silence, set this to a low number like 0.01.
Heads up: for certain codecs such as PCMA, a higher minimum threshold is required to detect complete silence, because their compressed payloads don't decode to actual silence โ they've got a residual DC offset instead. For PCMA the minimum value is 0.013.
Audio that gets detected as silence is replaced by comfort noise as specified by the cn-payload option (see below ๐). Currently this is applicable only to RTP peers that have advertised support for the CN RTP payload type โ in that case, the silence audio frames get replaced by CN RTP frames. ๐
--cn-payload=INTโ specify one comfort noise parameter. This option can be given multiple times, and the format follows RFC 3389. When you're at the command line, list the--cn-payload=option multiple times, each one carrying a single CN parameter. When you're in the config file, list the option only a single time and put multiple CN parameters separated by semicolons (e.g.cn-payload = 20;40;60).
The first CN payload value you give is the noise level, specified as -dBov per RFC 3389. That means a noise level of zero corresponds to maximum volume, while higher numbers correspond to 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 you list (the model order) is variable.
This option is applicable only to CN packets generated from the silence detection mechanism described just above. The configured CN parameters are used directly as the payload of CN packets sent by rtpengine.
The default values are 32 (-32 dBov) for the noise level, and no spectral information.
--player-cacheโ enable caching of encoded media packets for the media player. This applies to media playback initiated through theplay mediacommand. When it's on,rtpenginewon't just decode given media files and then encode the media to RTP on demand and on the fly โ instead it decodes and encodes each media file in full the first time playback is requested, then caches the resulting RTP packets in memory. This happens once for each media file and for each output RTP codec requested. ๐ง
Caching is done based on unique file name (with zero consideration given to different file names that might point at the same actual file), or by integer index for media files played from the database. No verification of changing content of files or database entries is done. Media files provided as a binary blob are also cached, although in that case a hash over the entire media file has to be performed โ so that usage is not recommended. ๐
It's not possible to choose a different start-pos for playback with this option enabled.
RTP data is cached and retained in memory for the lifetime of the process. It never leaves. It lives there now. ๐
--moh-max-duration=INTโ music-on-hold max possible duration, in ms. When it's not defined (set to0), it takes the1800000ms default value, which is half an hour. ๐ต--moh-max-repeats=INTโ music-on-hold max possible repeats.moh-max-durationalways takes precedence over it. By default it's always999if not defined otherwise.--moh-attr-name=STRINGโ controls the value to be added to the session level of SDP whenever MoH is triggered. If it's not defined, then it's not in use. ๐ธ--moh-prevent-double-holdโ protects against double MoH being played, like when twortpengineinstances in the interaction chain both inadvertently try to trigger MoH. ๐ญ
It works in combination with moh-attr-name, which must be defined in order to give rtpengine a clue about what the session level attribute for double hold detection is.
If it's not defined, then it's not in use.
--kernel-player=INT--kernel-player-media=INT
Enables and configures the kernel-based media player. It's disabled by default, only available if the kernel module is in use, and it requires player-cache to be enabled too. ๐
When enabled, media playback gets handled by a set of kernel threads. The kernel-player option defaults to zero and needs to be set to non-zero to enable the feature โ the number you hand it is the maximum number of concurrent kernel media players that can be used.
The kernel-player-media option configures the maximum number of unique media "files" that can be stored for playback in the kernel module. Media files requested for playback are first decoded by the player-cache feature, then handed to the kernel module in a pre-encoded format for quick playback. Defaults to 128.
Both player slots and media slots are shared among all instances of rtpengine running on a system using the same kernel module (even ones using different kernel table IDs). Unused slots use minimal resources, so no stress. โจ
--preload-media-files=FILEโ enables reading of media files at startup and caching them in memory for playback. Multiple files can be specified. On the command line, you have to give the option multiple times to do that โ while in the config file you give the option only once, with the list of files separated by semicolons. ๐
All listed files will be read into memory at startup and cached there for the lifetime of the daemon. So when somebody asks for one of these media files to play back, we don't go touching the disk at all โ we serve it straight from the cached copy. โจ
Big caveat tho: the file name in the play media request has to match the file name in the config option exactly. Like, character for character. If the name differs (or someone just requests a totally different file for playback), then playback happens from file as usual โ no cache, no magic. ๐
And bestie, there's a special string: on-demand. Slap that in instead of a file name and you unlock on-demand loading and caching of media files. Any file requested for playback that wasn't already chilling in the memory cache gets read once and then retained in the cache for the lifetime of the daemon. ๐ซถ
--media-files-reload=SECONDSโ spawns a background thread that periodically checks the media files kept in the memory cache and updates them if needed. Each file's modification timestamp gets checked against the last time it was read, and if it got updated since then, it's re-read and replaces the previous cached contents. ๐--preload-db-media=INTโ likepreload-media-filesbut pulling media from the database instead of reading files. Each entry has to be an integer pointing at an index from the database. On-demand loading works here too โ just supply the special stringon-demandinstead of an index number. ๐พ--db-media-reload=SECONDSโ same energy asmedia-files-reloadbut for media loaded from the database. Heads up tho: media stored in a database has no modification timestamp, which means all media will always be reloaded from the database at the given interval. No skipping, no shortcuts. ๐ญ--db-media-cache=PATHโ turns on filesystem-backed caching of media entries from the database. The path you give has to be readable and writeable by rtpengine. ๐
Whenever playback of media from the database is requested, rtpengine first checks if a corresponding cached file within the given path exists. If it exists โ media gets read from that file instead of from the database, slay. If it doesn't exist โ media gets read from the database, and then rtpengine creates the cache file so it's ready for the next time that same media is requested. ๐
--preload-db-cache=INTโ likepreload-db-mediabut it populates the filesystem-backed cache instead of storing the media in memory. On-demand loading is supported here too. โจ--cache-media-reload=SECONDSโ similar todb-media-reload, but it applies to media stored in the filesystem-backed cache. ๐ง
--expiry-timer=SECONDS--media-files-expire=SECONDS--db-media-expire=SECONDS--db-cache-expire=SECONDS
These four control the automatic removal of entries from the various media caches, when and if those entries go unused for a certain amount of time. By default, automatic removal is disabled โ nothing gets yeeted on its own. ๐
The option expiry-timer must be set to non-zero for any automatic removal to happen. It enables creation of a background thread and controls how often that thread runs to check for expired, unused cache entries.
The other options set the maximum allowed age for entries in their respective caches. If an entry wasn't used for longer than the given time, it gets removed from the cache. Bye bestie ๐
--audio-buffer-length=INTโ sets the buffer length used by the audio player (see below) in milliseconds. The default is500milliseconds. ๐ง
The buffer has to be long enough to accommodate at least two frames of audio from all contributing sources, which means at least 40 ms or 60 ms for most cases. And if media playback (via the play media command) is desired, then the buffer must be able to fit at least one full frame from the source media file โ and the length of that can vary depending on the format of the source media file. For 8 kHz .wav files this is 256 ms (2048 samples). So yeah, 500 ms is the recommended value, no cap.
--audio-buffer-delay=INTโ initial delay for new sources contributing to an audio buffer (used by the audio player, see below), in milliseconds. The default is5ms. โฑ๏ธ
This initial delay exists to compensate for varying inter-arrival times of media packets โ aka jitter. Set it too low and intermittent high jitter will give you gaps in the output audio ๐ญ. Set it too high and your output audio gets an unnecessary latency added to it. It's giving delicate balance.
--audio-player=on-demand|play-media|transcoding|alwaysโ defines when to enable the audio player if you're not explicitly instructing otherwise. The default setting ison-demand. ๐ต
Enabling the audio player for a party to a call makes rtpengine produce its own audio RTP stream, instead of just forwarding an audio stream received from elsewhere. The audio is generated from a circular audio buffer (see above) and all contributing audio sources get mixed into that one buffer. The contributing audio sources are audio streams received from elsewhere (the ones that would otherwise just be forwarded) plus audio produced by the play media command.
Here's the vibe check on each value:
on-demandโ the audio player is enabled only if explicitly requested by the user for a particular call, via theaudio-player=option used in a signalling message.play-mediaโ the audio player is enabled only while media playback via theplay mediacommand is active. Once playback finishes, the audio player goes back to being disabled and audio goes back to simply being forwarded.transcodingโ leaves the audio player disabled unless any sort of transcoding is required for a call.alwaysโ the audio player is enabled for all calls, unless explicitly disabled via theaudio-player=option used in a signalling message. This forces all audio through the transcoding engine, even if the input and output codecs are the same. ๐
Audio player usage can be changed on a call-by-call basis by including the audio-player= option in a signalling message. That option supports the values transcoding and always, which do exactly what's described up there, plus off โ which forces the audio player to be disabled regardless of this setting. Periodt.
--poller-per-threadโ enables thepoller per threadfunctionality: for every worker thread (see the--num-threadsoption) a poller will be created. With this on, it's guaranteed that only a single thread will ever read from a particular socket, which maintains the order of the packets. Might genuinely help if you're having issues with DTMF packets (RFC 2833). ๐--timer-accuracy=INTโ the minimum number of microseconds that a timer thread is allowed to sleep. In other words, if a timed event is scheduled sooner than this many microseconds in the future, then the event gets executed immediately. Set it to0for highest accuracy. Default is500microseconds (half a millisecond, bestie). โจ--io-uringโ enables experimental support for io_uring. Requires Linux kernel6.0or later. ๐ง
When enabled, instead of the usual polling mechanism, each worker thread sets up its own io_uring and uses it for polling, as well as for directly sending and receiving certain network data. In particular, userspace media data is sent and received directly via io_uring.
NOTE: as of the time of writing, worker threads sleeping in an io_uring poll get attributed to the host system as I/O wait CPU usage, with up to 99% CPU time spent in I/O wait (depending on the number of worker threads), but without being attributed to any process or thread. This is not actual CPU usage โ it just indicates time spent waiting for a network event, and so should be considered the same as idle CPU time. Don't panic when you see it. ๐
--io-uring-buffers=INTโ number of io_uring entries in the buffer allocated from the kernel, per thread. Defaults to16384. It must be large enough so that submission entries and completion entries are always available when needed. ๐ฅ--dtls-cert-cipher=prime256v1|RSAโ chooses the type of key to use for the signature used by the self-signed certificate used for DTLS. The previous default wasRSA. The current default and the only other option isprime256v1, which is a 256-bit elliptic-curve key. ๐--dtls-signature=SHA-256|SHA-1โ chooses the hash algorithm used for the signature of the self-signed certificate used for DTLS. The default isSHA-256. Do not confuse this with the hash algorithm used for the certificate fingerprint inserted into the SDP (a=fingerprint:), which is independent of the certificate's signature and can be selected during runtime. Yes this trips people up, iykyk. ๐--dtls-rsa-key-size=INTโ size in bits of the RSA key used by the DTLS certificate, if RSA is in use. Default is2048bits. ๐--dtls-ciphers=STRINGโ the ciphers allowed during the DTLS key exchange (again, not to be confused with the cipher used by the DTLS certificate). 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โ sets the DTLS MTU to enable fragmenting of large DTLS packets. Defaults to1200. The minimum value is576, since the internet protocol requires that hosts must be able to process IP datagrams of at least576bytes (for IPv4) or1280bytes (for IPv6). This does not preclude link layers with an MTU smaller than this minimum MTU from conveying IP data. Internet IPv4 path MTU is68bytes. ๐ง--mqtt-host=HOST|IPโ host or IP address of the Mosquitto broker to connect to. This must be set to enable exporting stats to Mosquitto. ๐ก--mqtt-port=INTโ port of the Mosquitto broker. Defaults to1883.--mqtt-id=STRINGโ client ID to use for Mosquitto. Default is a generated random string. ๐ฒ--mqtt-keepalive=INTโ keepalive interval in seconds. Defaults to30.--mqtt-user=USERNAME--mqtt-pass=PASSWORD
Credentials to connect 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--mqtt-tls-alpn=STRING
These 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. ๐
The tls-alpn can be set (e.g. mqtt) if a service like AWS IoT Core shares the same TLS port for two different network protocols. ๐
--mqtt-publish-qos=0|1|2๐
QoS value to use for publishing to Mosquitto. See the Mosquitto docs for the deets. ๐ซถ
--mqtt-publish-topic=STRINGโ the topic string you publish to Mosquitto with. Has to be a non-empty string, no empty vibes allowed, bestie. ๐ซ--mqtt-publish-interval=MILLISECONDSโ how often (in milliseconds) you publish to Mosquitto. Defaults to5000(aka 5 whole seconds, iykyk).--mqtt-publish-scope=global|summary|call|mediaโ what actually goes out in those messages:summary๐ซ โ one message gets published to Mosquitto every interval milliseconds, and it holds all the global stats.global๐ โ literally the same assummary, except it also carries a list of every running call, stats for each one included.call๐ โ one message per call gets published to Mosquitto with that call's stats, every interval milliseconds, plus one message every interval milliseconds with global stats.media๐ฌ โ one message per call media gets published (usually one media per call participant, so usually 2 media per call) with that call media's stats, every interval milliseconds, plus one message every interval milliseconds with global stats.
--mos=CQ|LQ|G.107|G.107.2|legacyโ these are the knobs that shape the MOS (Mean Opinion Score) calculation formula โจ. You can stack multiple options by passing multiple--mos=...arguments on the command line, or by using a semicolon-separated list in a singlemos=...line in the config file.
CQ and LQ are mutually exclusive, so only one of them can ever be in effect. CQ is the default โ that's conversational quality, it takes RTT into account, which means it needs peers to send RTCP correctly or it's gonna have a bad time ๐ญ. If you set LQ (listening quality) then RTT is straight-up ignored, so you can still get a MOS calculated even with zero RTCP. Very forgiving of you. ๐
The rest of the options all pick a MOS formula, and those are mutually exclusive too. The default is G.107, which is a simplified version of the G.107 formula. The one that used to be the default (and was the only option, iconic behavior) is legacy โ a custom formula that gives you slightly higher MOS values than G.107.
G.107.2 uses G.107.2 for fullband audio codecs and the simplified G.107 formula for everything else audio. Heads up: the full G.107.2 formula is kinda math-heavy ๐งฎ and gives higher MOS values for fullband audio codecs than plain G.107 does.
--measure-rtpโ turns on measuring RTP metrics even for plain RTP passthrough scenarios. Without it, RTP metrics only get measured in transcoding scenarios. ๐คท--reporting=full|inline|global|noneโ controls the format of the SSRC statistics (this is mainly a MOS-and-other-RTP-metrics thing) inside the responses todeleteandquerymessages.inlineโ the stats of media sources relevant to a media stream get tucked inside the reporting section that belongs to that media stream. Cozy. ๐globalโ a media stream's own section only lists the numeric SSRC identifiers relevant to it, and the actual statistics live in a global section, indexed by those SSRC identifiers.fullโ combines both of the above, which duplicates all the statistics and therefore makes message size grow significantly. ๐- The current default is
fullbecause that's the legacy format and some controlling agents require it. Not our fault, blame the agents. ๐
--rtcp-interval=INTโ the delay in milliseconds between RTCP packets when thegenerate-rtcpflag is on. The effective value also includes a random dispersion of 0..1 seconds on top, so the timer execution period is randomized and can be up to 1 second greater than the value you gave in ms. Defaults to5000ms (5 seconds).--socket-cpu-affinity=INTโ enables setting socket CPU affinity via theSO_INCOMING_CPUsocket option, if it's available. Default is zero, which disables the whole feature. If you set it 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 set, and values are used round-robin (e.g. set it to8and the values0through7get used to set affinity). If you set it to a negative number, then the number of available CPU cores is used instead. ๐ฅ--client-timeout=INT--client-retries=INTโ for requests sent to another NG peer, these configure the timeout and the maximum number of retries. The timeout is in milliseconds, must be larger than zero, and defaults to50. The number of retries must be larger than zero and defaults to5. Bestie, you got five shots. Make them count. ๐ฏ--transcode-config=STRโ sets the prefix for the sections in the config file that get used for transcode configurations. Peep the section TRANSCODE CONFIG below. ๐
๐ INTERFACES ๐
This section is all about the legacy syntax for configuring interfaces โ and the cute thing about it is you can use it equally from the configuration file or from the command line. The new syntax for configuring interfaces can only be used from the config file, and it's described at the end of this section. And ngl, the new one is now the preferred method because it supports additional options. Periodt. ๐
๐ Legacy Syntax ๐
The command-line options -i or --interface, or equivalently the interface config file option, specify local network interfaces for RTP. You need at least one, but you can totally specify multiple. The format of the value is:
[NAME/]IP[!IP]
where IP is either an IPv4 address, an IPv6 address, the name of a system network interface (like eth0), a DNS host name (like test.example.com), or any. ๐
Okay so do not confuse configuring a network interface by name with the logical interface name that rtpengine uses internally (more on that below). The NAME token in that syntax up there is the internal logical interface name, while the name of a system network interface goes in place of the first IP token. For example: to configure a logical network interface called int using all the addresses from the existing system network interface eth0, you'd write int/eth0. (And unless it's omitted, the second IP token โ the one used for the advertised address โ has to be an actual network address. It cannot be an interface name. Sorry. ๐ซ)
If DNS host names are used instead of addresses or interface names, the lookup happens only once, during daemon start-up. One and done. โจ
The special keyword any can be used to listen on any and all available local interface addresses, except from loopback devices. This keyword should only be given once, replacing a more explicit interface configuration.
To configure multiple interfaces with the command-line options, just present multiple -i or --interface options. But when you're using the config file, only use a single interface line โ and put multiple values in it separated by semicolons, like this:
interface = internal/12.23.34.45;external/23.34.45.54
๐ฆ System Network Interfaces ๐ฆ
If an interface option is given using a system interface name in place of a network address, and multiple network addresses are found configured on that system network interface, then rtpengine behaves as if you'd passed multiple --interface options. Like, for example, if interface eth0 exists with both addresses 192.168.1.120 and 2001:db8:85a3::7334 configured on it, and you give the option --interface=ext/eth0, then rtpengine would behave as if both of these had been specified:
--interface=ext/192.168.1.120
--interface=ext/2001:db8:85a3::7334
She does the work for you. ๐
๐ Advertised Address ๐
That second IP address after the exclamation point is optional, and you use it when the address you want to advertise in outgoing SDP bodies should be different from the actual local address. This is super useful in certain cases, like when your SIP proxy is sitting behind NAT. For example:
--interface=10.65.76.2!192.0.2.4
means 10.65.76.2 is the actual local address on the server, but outgoing SDP bodies should advertise 192.0.2.4 as the address endpoints should talk to. ๐ฃ๏ธ
โ ๏ธ Heads up: you may have to escape the exclamation point from your shell when using command-line options โ so like, \!. Your shell will absolutely eat it otherwise. ๐ญ
๐ธ Interface Names ๐ธ
Giving an interface a name (separated from the address by a slash) is optional; if you omit it, the name default gets used. Names are useful because they let you create logical interfaces made of one or more local addresses. Then you can tell rtpengine to use particular interfaces when it's processing an SDP message, so it uses different local addresses when talking to different endpoints. The most common use case for this is bridging between one or more private IP networks and the public internet. ๐
For example: if clients coming from a private IP network must communicate their RTP with the local address 10.35.2.75, while clients coming from the public internet must communicate with your other local address 192.0.2.67, you could create one logical interface pub and a second one priv like this:
--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). ๐ซ
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 is the one that gets used.
๐ง Multiple Addresses per Interface ๐ง
You can specify multiple addresses for the same logical interface (same name). Most commonly that's one IPv4 address and one IPv6 address, like so:
--interface=192.168.63.1 --interface=fe80::800:27ff:fe00:0
In this example no interface name is given, so both addresses get added to a logical interface named default. Then you'd use the address family option to tell rtpengine which address to use in a particular case. ๐
It's also possible to have multiple addresses of the same family on a logical network interface. In that case, the first address (of a particular family) given for an interface is the primary address used by rtpengine for most purposes. Any additional addresses get advertised as additional ICE candidates with increasingly lower priority. This one's great on multi-homed systems โ it lets endpoints pick the best possible path to reach the RTP proxy. ๐ฃ๏ธ Just know that if ICE isn't being used, those additional addresses go unused, even though ports would still get allocated on those interfaces. ๐
๐ซ Round-Robin Address Selection ๐ซ
okay bestie here's another little trick โจ instead of plain interface names, you can hand over names in the format BASE:SUFFIX. Why would you do that? Because it lets interfaces get used in a round-robin fashion, which is super handy when you're load-balancing the port ranges across a whole bunch of interfaces ๐ฅ
Say what now? Take this config for example: --interface=pub:1/192.0.2.67 --interface=pub:2/10.35.2.75. Those two interfaces can still be referenced directly by their full names (like direction=pub:1, no drama ๐
), but now you can also reference just the base name on its own (i.e. direction=pub). And when you use the base name, one of the two interfaces gets selected round-robin style โ but only if that interface actually has enough open ports available. Rude to pick an interface with no ports, so it doesn't ๐
The whole point? This makes it possible to effectively increase the number of available media ports across multiple IP addresses. And there is no limit on how many interfaces can share the same base name. Go off, honestly ๐
You can also combine the BASE:SUFFIX notation with specifying multiple addresses for the same interface name. An advanced example could be (this one's in config file notation, and we're omitting the actual network addresses, obviously):
interface = pub:1/IPv4;pub:1/IPv4;pub:1/IPv6;pub:2/IPv4;pub:2/IPv6;pub:3/IPv6;pub:4/IPv4
So in this lil example: when direction=pub and IPv4 is needed as a primary address, either pub:1, pub:2, or pub:4 might get selected. When pub:1 is the chosen one, 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 at all. And 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 there aren't enough ports available on any interface? It will not be selected by the round-robin algorithm. No ports no service, that's the vibe check ๐ญ
Oh and โ plot twist โ you can use the round-robin algorithm even if the direction is not given. If the first given interface has the BASE:SUFFIX format, then the round-robin algorithm is used and it'll select interfaces with the same BASE name. Sneaky but iconic ๐
๐ท๏ธ Alias Names ๐ท๏ธ
Interface alias names can be created using the ALIAS=NAME syntax. Important detail bestie, don't skip: the alias must be listed after the primary interface that it references. So to create an actual logical interface pub1 and then an alias pub pointing at it:
interface = pub1/IPv4;pub=pub1
Interface aliases are useful in combination with Redis replication. Here's the tea โ if an interface is referred to via an alias name (e.g. direction=pub), then the interface's actual name (pub1 in this example) is propagated into the Redis storage and thus to any dependent standby instances. Those standby instances can then have different address configurations for that interface, which makes it possible to facilitate failover with static addressing (for example behind an IP load balancer). Chef's kiss ๐ซถ
๐ฐ๏ธ Legacy Protocols ๐ฐ๏ธ
If you are not using the NG protocol but rather the legacy UDP protocol used by the rtpproxy module, then listen up: the interfaces must be named internal and external, corresponding to the i and e flags, if you wish to use network bridging in this mode. That's just how the old-school era rolls, no cap ๐
๐ New Configuration-File Based Syntax ๐
When a configuration file is in use, instead of having to list multiple interfaces in one long line in the config file, it's now possible to use config file sections (or "groups") for a more convenient way to configure and manage multiple interfaces โจ
To use it: instead of setting the interface option to any value, the option interfaces-config must be set to a non-empty string. This string is a prefix, which rtpengine uses to go look in the config file for config sections (groups) that contain interface configurations.
For example, if the setting interfaces-config = interface is present in the config file, rtpengine would consider config file sections starting with the string interface and followed by a dash to be interface configurations. The remainder of the name of the config section (the part after the dash) becomes the default name of the interface. The name for the interface can then be overridden within the config section (see below ๐).
NOTE: The names of config sections must be unique within the config file, and each interface config can list only a single address. To add multiple addresses to the same logical interface, the name of the logical interfaces must necessarily be explicitly set in each config section, instead of relying on the name extracted from the name of the config section. Planning ahead is the main character move here ๐
Each config section must at least define an interface address by setting the address option, or define an alias interface (as described above) by setting the alias option. The name option can be set to override the default name extracted from the name of the config section.
Non-alias interfaces support the additional option advertised to set the advertised address, as well as port-min and port-max to set a port range different from the global setting, and exclude-ports taking a list of semicolon-separated port numbers to exclude individual ports from the port range. Multiple interfaces sharing the same IP address are allowed to use different, possibly overlapping, port ranges. Round-robin interface usage is supported in the same way as described above, i.e. by using a colon and a suffix as part of the interface name ๐ธ
Interface sections are processed in order, and as such the first one listed becomes the default interface. And if both legacy syntax and new configuration-file based syntax are in use? Then interfaces from the legacy syntax are processed first. Order matters, bestie ๐ง
A complete example (and she ate, honestly):
[rtpengine]
interfaces-config = interface
# Create an interface "default" and pick up any non-local addresses
# that are bound to the system at startup.
[interface-default]
address = any
# Create an interface "external" and pick up any addresses bound
# to the physical interface "enp63s0".
[interface-external]
address = enp63s0
# Create an interface "internal" with an explicitly set address,
# and also set the advertised address.
[interface-internal]
address = 192.168.67.43
advertised = 203.0.113.7
# Create an interface "ICE" and add all addresses bound to the
# physical interface "enp63s0". Use a mismatched but unique name for
# the interface section so more addresses can be added to this
# interface.
[interface-ICE-1]
name = ICE
address = enp63s0
# Add addresses from interface "enp35s0" to the interface "ICE"
# and use a different port range.
[interface-ICE-2]
name = ICE
address = enp35s0
port-min = 10000
port-max = 59999
exclude-ports = 20000; 20001; 20002
# Create an alias interface "virt" pointing to "external".
[interface-virt]
alias = external
# Create two interfaces that will be used in a round-robin way
# by referring to the interface "rr".
[interface-rr:0]
address = enp5p0
exclude-ports = 29999
[interface-rr:1]
address = enp15p0
๐ฃ SIGNALLING TEMPLATES ๐ฃ
ok so here's the situation bestie ๐ฎโ๐จ a lot of what rtpengine does is controlled by flags and keys/values that you hand it at runtime, as part of the signalling control protocol used for communication between the controlling agent (e.g. a SIP proxy) and the rtpengine process. Which means there's often a need to repeatedly give the exact same set of default flags and values to rtpengine for every single message sent to it. And that leads to controlling scripts that are hard to maintain or hard to read. Not the vibe ๐ญ
To alleviate this problem, rtpengine supports signalling templates that can be configured in its main configuration file and can then be referred to by short names. Short names >>> wall of flags, periodt ๐
To use this feature, a configuration file must be in use (by default /etc/rtpengine/rtpengine.conf) and the configuration key templates=... must be set to a non-empty string. The value gives the name of the section in the configuration file that contains signalling templates. So for example, if the value is set to templates=templates, then the section [templates] will be used to read signalling templates โจ
Each key/value in this file section then corresponds to one signalling template, and can be referred to via template=... in any control message.
For example, in order to make an offer to a WebRTC-compliant client, a Kamailio or OpenSIPS script may have used:
rtpengine_offer("transport-protocol=UDP/TLS/RTP/SAVPF ICE=force trickle-ICE rtcp-mux=[offer require] no-rtcp-attribute SDES=off generate-mid");
This entire string of flags can now be converted into a signalling template in the config file as such:
[rtpengine]
...
templates = templates
...
[templates]
WebRTC = transport-protocol=UDP/TLS/RTP/SAVPF ICE=force trickle-ICE rtcp-mux=[offer require] no-rtcp-attribute SDES=off generate-mid
And the offer command in Kamailio or OpenSIPS can then simply be turned into:
rtpengine_offer("template=WebRTC");
Look how clean ๐ญโจ obsessed.
In addition to named signalling templates, rtpengine supports default signalling templates that are automatically applied. Default signalling templates are templates using a name that matches a signalling command (e.g. offer or start recording), or the special name default, which is a template that is applied to all signalling messages. These templates are automatically applied without needing to refer to them by name using template=... from within the signalling message. Automatic glow-up, she doesn't even need to be called ๐
๐๏ธ TRANSCODE CONFIG ๐๏ธ
Okay bestie, so if the setting transcode-config is set to a non-empty string, then rtpengine goes looking in the configuration file for sections that start with the prefix strings you gave it (plus a dash) and treats those as transcode configurations. Like, say you set transcode-config = tx โ then sections in the config file like tx-1 or tx-foobar get treated as transcode configs. And heads up: the names of all sections in the config file have to be unique. No duplicates, periodt. ๐
Every transcoding section has to list a source codec as source=... and a destination codec as destination=.... Codecs can just be named by their basic name (like PCMA) or with a full complete SDP-like definition (like opus/48000/2). Each section also has to list an action or a verdict. Non-negotiable. โจ
So for example, if source=PCMU and destination=PCMA are set, then that particular transcoding config section is the one that gets considered when the received codec is PCMU and the requested output codec is PCMA. Slay.
๐ธ preference Verdict ๐ธ
This verdict can be used all by itself, or it can vibe alongside some other verdict. What it does is configure a preference for a particular codec combination, relative to the other codec combinations. The default preference (nothing configured, just chilling) is zero. Codec combos with higher (positive) preference values get considered as better options than the default, while lower (negative) preference values get considered as worse options. It's literally just ranking.
For example, consider the following configuration:
transcode-config = tc
[tc-PCMA-PCMU-higher]
source = PCMA
destination = PCMU
preference = 5
[tc-GSM-G723-lower]
source = G723
destination = GSM
preference = -5
Now imagine a received offer SDP listing codecs GSM, PCMU, and G722 (in that order), with transcoding to PCMA and G723 requested. The outgoing offer SDP would therefore list GSM, PCMU, G722, PCMA, and G723 (in that order). Same order, just with the transcoding targets tacked on the end. ๐ซถ
If the answer were to accept any of GSM, PCMU, or G722, then pass-through without transcoding would occur. Easy, no work needed, love that for us.
If the answer were to accept any of the other codecs that were requested for transcoding (PCMA or G723), then normally the first offered codec (GSM) would be used as the transcoding partner. But bestie, there's a but:
- If the answer were to accept
PCMA, thenGSMas transcoding partner would be considered with a preference of zero, as no config section matches this pair. However the config sectiontc-PCMA-PCMU-highermatchesPCMUas transcoding partner forPCMAand would give that pair a higher preference (5). SoPCMUwould win with the highest preference, and transcoding would occur betweenPCMAandPCMU. ๐ - If the answer were to accept
G723, then the config sectiontc-GSM-G723-lowerwould be considered and would giveGSMas transcoding partner a negative preference of-5. The next possible codec (PCMU) doesn't have a matching config section and so would be considered with a preference of zero. SoPCMUwould win and transcoding would occur betweenG723andPCMU. ๐ (in a good way)
NOTE: These config sections operate directionally, meaning that in the above example, a config section listingsource = GSManddestination = G723would not be considered. If a codec pair ought to receive the same preference value regardless of the direction, then it must be listed twice, withsourceanddestinationswapped. Yes, twice. It's giving extra homework but that's just how it is. ๐ญ
๐ transform Verdict ๐
To use the transform verdict, the config key transform=... must be set. The value is a remote NG peer endpoint โ like the NG control port of another rtpengine instance โ in the usual address:port notation. This tells rtpengine to send a transform NG request to the remote peer, asking it to remotely transcode the media. If it succeeds, rtpengine will forward the unmodified media stream to the remote peer, to the address and port it received from the NG response, and will expect the transcoded media to be sent back in return, which it will then forward to its appropriate destination. In other words โ it facilitates a designated remote transcoding node, instead of doing the transcoding locally. Outsourcing, but make it media. โจ
Optional config keys you can set are:
local-interface=...โ to select the local interface to use to communicate with the remote transform gateway.remote-interface=...โ to tell the remote gateway to use a particular interface on its side.address-family=...โ to select betweenIPv4andIPv6.
๐ช EXIT STATUS ๐ช
0โ Successful termination. She ate and left no crumbs. ๐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--redishas been specified, in which case the one specified in--rediswill be used for read operations only. Read-only bestie, she's not doing writes. ๐
๐ FILES ๐
/etc/rtpengine/rtpengine.confโ Configuration file. The main character of the config world. ๐
๐ง 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
Copy it, run it, watch it slay. ๐ฅ
๐ SEE ALSO ๐
And that's a wrap on the whole rtpengine(8) page, bestie!! ๐ If you ever need to double-check which version of these facts you're reading, this one's 26.0.1.21+0~mr26.0.1.21, dated 2026-07-31. Screenshot it, cite it, live your truth. ๐ฆโจ
๐ฆ where this page comes from
- ubuntu stonking: rtpengine-daemon 26.0.1.21-1 ยท GPL-3.0-or-later WITH OpenSSL exception
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms