Skip to main content

Chapter 17: Media Handling

FreeSWITCH gives the operator explicit control over whether RTP audio and video flows through the server, passes through it transparently, or is routed end-to-end between endpoints without server involvement. This chapter covers the three media modes, the profile parameters and channel variables that select them, early media, late codec negotiation, RTP inactivity timeouts, jitter buffering, SRTP/SDES secure media configuration, and additional RTP behavior parameters.

Media Modes

FreeSWITCH supports three distinct modes that determine how RTP flows between call legs.

FreeSWITCH in the Media Path

This is the default mode. FreeSWITCH terminates RTP on both legs independently. It decodes, processes, and re-encodes media internally. Transcoding between codecs, recording, DTMF detection, jitter buffering, and hold music all require this mode. The server allocates two RTP ports per call (one per leg) and is fully visible in the media path.

Proxy Media Mode

In proxy media mode, FreeSWITCH passes RTP packets between endpoints transparently without decoding the payload. The server still allocates RTP ports for both legs and remains in the media path at the IP/UDP layer, but it does not inspect or modify the RTP payload. SDP is rewritten so each endpoint sends RTP to FreeSWITCH's ports, but packets are forwarded with minimal processing. Transcoding is not possible in this mode.

Bypass Media Mode

In bypass media mode (also referred to as no-media mode), FreeSWITCH rewrites the SDP to direct the two endpoints to exchange RTP directly with each other. Once the call is established, RTP does not touch the FreeSWITCH host at all. Transcoding, recording, DTMF detection on the RTP stream, hold music injection, and other media processing are unavailable. The server handles signaling only.

Mode Comparison Table

ModeRTP PathHow to Enable
Normal (default)Both legs terminate at FreeSWITCH; full media processingDefault; no special configuration required
Proxy mediaBoth legs terminate at FreeSWITCH ports; payload forwarded verbatimproxy_media=true channel variable or inbound-proxy-media=true profile param
Bypass mediaEndpoints exchange RTP directly; FreeSWITCH handles signaling onlybypass_media=true channel variable or inbound-bypass-media=true profile param (alias: inbound-no-media)

Setting Media Mode

Profile-Level Defaults

The following parameters in a Sofia SIP profile set the media mode for all inbound calls handled by that profile. They are configured inside the <settings> block of the profile XML file (for example, conf/sip_profiles/internal.xml).

ParameterPurposeAccepted ValuesDefault
inbound-bypass-mediaForce all inbound calls to bypass media mode. Alias: inbound-no-media. Implicitly enables inbound-late-negotiation.true, falsefalse
inbound-proxy-mediaForce all inbound calls to proxy media modetrue, falsefalse
inbound-late-negotiationDefer A-leg codec selection until after the dialplan runstrue, falsefalse (see section 17.4.1)
disable-transcodingForce the outbound bridge leg to offer only the codec the inbound leg selected, preventing transcoding without leaving the normal media pathtrue, falsefalse

inbound-bypass-media implicitly enables inbound-late-negotiation. Both bypass and proxy profile parameters are disabled by default in the vanilla configuration; inbound-late-negotiation is enabled in the vanilla internal profile.

<!-- conf/sip_profiles/internal.xml -->

<!-- Bypass media for all inbound calls on this profile -->
<!--<param name="inbound-bypass-media" value="true"/>-->

<!-- Proxy media for all inbound calls on this profile -->
<!--<param name="inbound-proxy-media" value="true"/>-->

<!-- Disable transcoding: outbound leg offers only the inbound codec -->
<!--<param name="disable-transcoding" value="true"/>-->

Per-Call Channel Variables

Media mode can be controlled per call in the dialplan using channel variables. Set these variables before bridging.

VariablePurposeValuesDefault
bypass_mediaEnable or disable bypass media mode for a calltrue, falseunset
proxy_mediaEnable or disable proxy media mode for a calltrue, falseunset

When bypass_media is true, FreeSWITCH sets the CF_PROXY_MODE channel flag, directing the SDP to route RTP between the endpoints directly. When proxy_media is true, FreeSWITCH sets CF_PROXY_MEDIA, keeping RTP flowing through the server's allocated ports but without payload inspection.

If both variables are set, proxy_media takes precedence over bypass_media.

Dialplan example: bypass media

<extension name="bypass-example">
<condition field="destination_number" expression="^1234$">
<action application="set" data="bypass_media=true"/>
<action application="bridge" data="sofia/external/1234@carrier.example.com"/>
</condition>
</extension>

Dialplan example: proxy media

<extension name="proxy-example">
<condition field="destination_number" expression="^5678$">
<action application="set" data="proxy_media=true"/>
<action application="bridge" data="sofia/external/5678@carrier.example.com"/>
</condition>
</extension>

media-option Behaviors

The media-option profile parameter adjusts media mode behavior for specific events. Multiple values can be set by repeating the parameter. proxy-hold=true is incompatible with resume-media-on-hold and bypass-media-after-hold; FreeSWITCH logs a warning and clears the conflicting option at profile load time.

ValueEffect
resume-media-on-holdWhen an endpoint in bypass or proxy mode presses hold, FreeSWITCH pulls the call back into the normal media path to allow hold music injection.
bypass-media-after-att-xferAfter a completed attended transfer, the call returns to bypass media mode.
bypass-media-after-holdAfter the hold ends, the call returns to bypass media mode. Requires resume-media-on-hold to also be set.
noneClears all previously set media options.
<!-- conf/sip_profiles/internal.xml -->
<param name="media-option" value="resume-media-on-hold"/>
<param name="media-option" value="bypass-media-after-hold"/>

Early Media

Early media allows FreeSWITCH to establish an RTP stream before a call is answered. It is used to pass ringback tone, IVR prompts, or other audio to the caller during the alerting phase.

The pre_answer dialplan application sends a 183 Session Progress response with an SDP, establishing one-way early media from FreeSWITCH to the caller. The call remains in an unanswered state until answer is explicitly executed.

<extension name="early-media-example">
<condition field="destination_number" expression="^9001$">
<action application="pre_answer"/>
<action application="playback" data="ivr/ivr-please_hold_while_connect.wav"/>
<action application="bridge" data="sofia/internal/1001@${domain}"/>
</condition>
</extension>

Early media and bypass media mode interact: if bypass_media is set and the call has early media (CF_EARLY_MEDIA flag) but has not yet been answered, FreeSWITCH clears the early media state before activating bypass mode to avoid a signaling conflict.


Late Negotiation

inbound-late-negotiation

By default, FreeSWITCH selects a codec for the inbound leg as soon as the INVITE arrives, before the dialplan executes. With inbound-late-negotiation enabled, codec selection for the A-leg is deferred until after the dialplan has run. This allows dialplan logic to influence codec selection and is required for some advanced routing scenarios.

<!-- conf/sip_profiles/internal.xml -->
<param name="inbound-late-negotiation" value="true"/>

The vanilla internal profile ships with this parameter enabled (true).

ParameterPurposeAccepted ValuesDefault
inbound-late-negotiationDefer A-leg codec selection until after the dialplan runstrue, falsefalse

inbound-proxy-media

inbound-proxy-media is a profile-level shorthand for enabling proxy media mode on all inbound calls. See the table in section 17.2.1. When set, it is equivalent to setting proxy_media=true on every inbound call channel.


RTP Timeouts

FreeSWITCH can terminate a call when no RTP activity is detected for a configurable period.

Current Mechanism: Channel Variables

The preferred mechanism is per-channel variables. These values are in milliseconds.

VariablePurposeValuesNotes
media_timeoutMilliseconds of RTP inactivity on an active call before hangupInteger (ms), 0 to disableIf media_hold_timeout is unset, it defaults to 10x media_timeout
media_hold_timeoutMilliseconds of RTP inactivity while on hold before hangupInteger (ms), 0 to disableOverrides the automatic 10x default
media_timeout_audioAudio-specific override of media_timeoutInteger (ms)Takes precedence over media_timeout for the audio engine
media_hold_timeout_audioAudio-specific override of media_hold_timeoutInteger (ms)Takes precedence over media_hold_timeout for the audio engine
media_timeout_videoVideo-specific override of media_timeoutInteger (ms)Takes precedence over media_timeout for the video engine
media_hold_timeout_videoVideo-specific override of media_hold_timeoutInteger (ms)Takes precedence over media_hold_timeout for the video engine
<extension name="timeout-example">
<condition field="destination_number" expression="^.*$">
<!-- 120 000 ms = 120 seconds -->
<action application="set" data="media_timeout=120000"/>
<!-- 600 000 ms = 600 seconds -->
<action application="set" data="media_hold_timeout=600000"/>
<action application="bridge" data="sofia/internal/${destination_number}@${domain}"/>
</condition>
</extension>

Deprecated Profile Parameters

The profile parameters rtp-timeout-sec and rtp-hold-timeout-sec are deprecated. FreeSWITCH logs a WARNING to the console whenever they are parsed at profile load. Their values are in seconds. They remain functional in the current codebase and continue to appear in the vanilla internal.xml for backward compatibility, but new deployments should use the channel variables in section 17.5.1 instead.

ParameterPurposeAccepted ValuesDefault (vanilla)
rtp-timeout-secSeconds of RTP inactivity on an active call before hangup. Deprecated.Integer (seconds), 0 to disable300
rtp-hold-timeout-secSeconds of RTP inactivity while on hold before hangup. Deprecated.Integer (seconds), 0 to disable1800

Deprecated channel variables rtp_timeout_sec and rtp_hold_timeout_sec also exist and are logged as deprecated with the same warning. Use media_timeout and media_hold_timeout instead.

execute_on_media_timeout

A media timeout causes FreeSWITCH to hang up the call with SWITCH_CAUSE_MEDIA_TIMEOUT. Setting the execute_on_media_timeout channel variable to a dialplan application name causes FreeSWITCH to execute that application when the timeout fires, allowing custom handling such as logging or notification.

The behavior depends on which code path detects the timeout:

  • Read-frame path: when the timeout is detected while the channel is reading frames, FreeSWITCH executes the execute_on_media_timeout application as the timeout handler.
  • RTP-thread timeout path (switch_rtp.c): the dedicated RTP timeout check (check_timeout) executes the execute_on_media_timeout application and then still hangs up the call with SWITCH_CAUSE_MEDIA_TIMEOUT. On this path the application runs first, but the hangup is unconditional.

So execute_on_media_timeout is best treated as a hook that runs alongside the timeout rather than a guaranteed replacement for the hangup.

<action application="set" data="execute_on_media_timeout=hangup"/>

Jitter Buffer

The software jitter buffer is activated per channel via the jitterbuffer_msec channel variable. It is only available in normal (non-proxy, non-bypass) media mode.

VariablePurposeFormatRange
jitterbuffer_msecTarget buffer depth and optional maximum depthN or N:M (milliseconds); suffix p treats N/M as packet counts10 to 10000 ms (see note)

When jitterbuffer_msec is set to a value with a p suffix (for example, 4p), the number is treated as a packet count rather than milliseconds and multiplied by the codec packet duration. The optional :M component sets the maximum buffer depth; if omitted, the maximum defaults to 50 times the target value.

The supported range is 10 to 10000 ms, but FreeSWITCH does not clamp out-of-range values to those bounds. If the resolved target falls below 10 ms or above 10000 ms, the value is discarded and reset to a single-packet default (the codec packet duration multiplied by 1), with the maximum recomputed as 100 times that one-packet target. A value such as 5 or 20000 therefore does not become 10 or 10000; it collapses to roughly one packet of buffering.

<!-- 60 ms target, default maximum -->
<action application="set" data="jitterbuffer_msec=60"/>

<!-- 60 ms target, 300 ms maximum -->
<action application="set" data="jitterbuffer_msec=60:300"/>

<!-- 4-packet target -->
<action application="set" data="jitterbuffer_msec=4p"/>

The rtp_jitter_buffer_plc channel variable controls packet loss concealment within the jitter buffer. It defaults to true; set it to false to disable PLC.


Additional RTP Parameters

The following profile parameters control RTP behavior beyond media mode and timeouts.

ParameterPurposeAccepted ValuesDefault
rtp-autofix-timingAttempt to correct RTP timing anomalies from endpoints that send at irregular packet ratestrue, falsetrue
disable-rtp-auto-adjustDisable automatic RTP source IP/port learning (re-INVITE NAT traversal)true, falsefalse (auto-adjust enabled)
rtp-rewrite-timestampsRewrite RTP timestamps on forwarded packets rather than passing them throughtrue, falsefalse
pass-rfc2833Pass RFC 2833 DTMF telephone-event packets through transparently rather than terminating themtrue, falsefalse

The channel variable rtp_media_autofix_timing (true/false) overrides the rtp-autofix-timing profile setting on a per-call basis.

<!-- conf/sip_profiles/internal.xml -->
<!--<param name="rtp-autofix-timing" value="false"/>-->
<!--<param name="disable-rtp-auto-adjust" value="true"/>-->
<!--<param name="rtp-rewrite-timestamps" value="true"/>-->
<!--<param name="pass-rfc2833" value="true"/>-->

Secure Media with SRTP

FreeSWITCH supports Secure RTP (SRTP) with key exchange via SDES (SDP security descriptions, RFC 4568). The rtp_secure_media channel variable and the global rtp_sdes_suites variable control SRTP negotiation.

rtp_secure_media Channel Variable

The rtp_secure_media channel variable controls whether FreeSWITCH offers, accepts, or rejects SAVP (Secure Audio/Video Profile) in SDP negotiation.

ValueBehavior
mandatory or trueAccept or offer SAVP only. Calls that cannot negotiate SRTP are rejected.
optionalOffer and accept both SAVP and AVP; prefer SAVP when available.
forbidden or falseDeny SAVP negotiation. Calls offering SRTP only will fail.
unset (inbound)Defaults to optional: accept SAVP if offered, do not require it.
unset (outbound)Defaults to forbidden: do not offer SRTP.

Two directional variants restrict the setting to a single leg:

  • rtp_secure_media_inbound applies to inbound SDP offers only.
  • rtp_secure_media_outbound applies to outbound SDP offers only.

When a directional variable is set, it takes precedence over rtp_secure_media for that direction. FreeSWITCH checks the directional variable first; if unset, it falls back to rtp_secure_media.

Dialplan example: require SRTP on both legs

<extension name="secure-call">
<condition field="destination_number" expression="^9900$">
<action application="set" data="rtp_secure_media=mandatory"/>
<action application="bridge" data="sofia/external/9900@secure.example.com"/>
</condition>
</extension>

Dialplan example: require SRTP inbound, optional outbound

<action application="set" data="rtp_secure_media_inbound=mandatory"/>
<action application="set" data="rtp_secure_media_outbound=optional"/>

Note: When using SRTP, do not offer or accept variable bit rate codecs (such as variable-rate Opus or AMR). Doing so leaks information about the payload through packet size patterns, potentially compromising the SRTP stream. Use fixed-rate codecs (for example, PCMU, PCMA, G722, or Opus at a fixed bitrate) with SRTP.

Crypto Suite Selection

Specific SRTP crypto suites can be appended to the rtp_secure_media value as a colon-separated list. FreeSWITCH will offer or accept only the listed suites, in the specified order.

<!-- Offer two specific suites, strongest first -->
<action application="set" data="rtp_secure_media=mandatory:AES_CM_256_HMAC_SHA1_80:AES_CM_128_HMAC_SHA1_80"/>

<!-- Optional SRTP, one suite only -->
<action application="set" data="rtp_secure_media=optional:AEAD_AES_256_GCM_8"/>

<!-- Inbound-specific suite constraint -->
<action application="set" data="rtp_secure_media_inbound=mandatory:AEAD_AES_256_GCM_8"/>

Alternatively, set the suite list independently with rtp_secure_media_suites and use a plain mode value (without appended suites) in rtp_secure_media:

<action application="set" data="rtp_secure_media_suites=AEAD_AES_256_GCM_8:AES_CM_256_HMAC_SHA1_80"/>
<action application="set" data="rtp_secure_media=mandatory"/>

The supported crypto suites are listed in the table below.

Suite NameDescription
AEAD_AES_256_GCM_8AES-256 GCM with 8-octet authentication tag (RFC 5116)
AEAD_AES_256_GCMAES-256 GCM with full 16-octet authentication tag (RFC 7714)
AEAD_AES_128_GCM_8AES-128 GCM with 8-octet authentication tag (RFC 5116)
AEAD_AES_128_GCMAES-128 GCM with full 16-octet authentication tag (RFC 7714)
AES_CM_256_HMAC_SHA1_80AES-256 Counter Mode with 80-bit HMAC-SHA1 authentication tag
AES_CM_192_HMAC_SHA1_80AES-192 Counter Mode with 80-bit HMAC-SHA1 authentication tag
AES_CM_128_HMAC_SHA1_80AES-128 Counter Mode with 80-bit HMAC-SHA1 authentication tag (SRTP default)
AES_CM_256_HMAC_SHA1_32AES-256 Counter Mode with 32-bit HMAC-SHA1 authentication tag
AES_CM_192_HMAC_SHA1_32AES-192 Counter Mode with 32-bit HMAC-SHA1 authentication tag
AES_CM_128_HMAC_SHA1_32AES-128 Counter Mode with 32-bit HMAC-SHA1 authentication tag
AES_CM_128_NULL_AUTHAES-128 Counter Mode with no authentication. Not recommended (see RFC 3711 Section 7.5).

Global Suite List with rtp_sdes_suites

The rtp_sdes_suites preprocessor variable in conf/vars.xml sets the default ordered list of SRTP suites offered system-wide when no per-call suite override is in effect. Suites are separated by \|.

<!-- conf/vars.xml -->
<X-PRE-PROCESS cmd="set" data="rtp_sdes_suites=AEAD_AES_256_GCM_8|AEAD_AES_128_GCM_8|AES_CM_256_HMAC_SHA1_80|AES_CM_192_HMAC_SHA1_80|AES_CM_128_HMAC_SHA1_80|AES_CM_256_HMAC_SHA1_32|AES_CM_192_HMAC_SHA1_32|AES_CM_128_HMAC_SHA1_32|AES_CM_128_NULL_AUTH"/>

The vanilla default lists all supported suites from strongest to weakest. FreeSWITCH selects the strongest suite that both endpoints share. Narrow this list to restrict acceptable suites globally.

DTLS-SRTP and WebRTC

WebRTC endpoints (browsers and WebRTC-capable SIP clients connecting over WebSocket or Secure WebSocket) use DTLS-SRTP (RFC 5764) rather than SDES for key exchange. DTLS-SRTP is negotiated automatically when FreeSWITCH detects an AVPF SDP profile from the remote endpoint, which is standard for WebRTC. The rtp_secure_media SDES mechanism described in this chapter does not apply to DTLS-SRTP negotiation.

For WebRTC configuration, including WebSocket bindings (ws-binding, wss-binding), DTLS certificate setup, and ICE/STUN configuration, see Chapter 9: WebRTC with Verto and Chapter 10: WebRTC over SIP (WSS).


NAT Traversal

The NAT Problem

When FreeSWITCH runs on a host behind a Network Address Translator, it binds to the host's private IP address. By default it inserts that private IP into SIP Contact and Via headers and into the SDP c= and m= connection lines. A far-end endpoint on the public Internet receives these private addresses and cannot route RTP back to FreeSWITCH, causing one-way audio or no audio at all.

The solution has two parts:

  1. Configure FreeSWITCH to advertise the public IP address in SIP and SDP (ext-sip-ip, ext-rtp-ip).
  2. Handle far-end registrants and callers that are themselves behind NAT (aggressive-nat-detection, apply-nat-acl, NDLB-force-rport, RTP auto-adjust).

Both parts are configured at the SIP profile level.

Advertising the Public Address: ext-sip-ip and ext-rtp-ip

ext-sip-ip and ext-rtp-ip are Sofia SIP profile parameters. They tell FreeSWITCH which address to advertise externally. They do not change the local bind address (sip-ip, rtp-ip); they only override the address placed in outgoing SIP headers and SDP.

ParameterAdvertised inPurpose
ext-sip-ipSIP Contact, Via, Record-RoutePublic IP for SIP signaling
ext-rtp-ipSDP c= and m= connection linesPublic IP for RTP media

When ext-sip-ip is set and ext-rtp-ip is not, ext-rtp-ip inherits the resolved value of ext-sip-ip. The reverse is not true: ext-rtp-ip does not imply ext-sip-ip.

The local bind parameters, sip-ip and rtp-ip, accept auto or a literal IP and determine which local interface FreeSWITCH listens on. Setting sip-ip or rtp-ip to auto causes FreeSWITCH to use its best-guess local IP (local_ip_v4). They also accept the interface: prefix (interface:eth0, interface:ipv4/eth0, interface:ipv6/eth0) to bind to a specific network interface.

ext-ip Value Forms

Both ext-sip-ip and ext-rtp-ip accept the same set of value forms.

Value FormResolution Method
203.0.113.10Literal IPv4 address; used exactly as given
autoFreeSWITCH uses its best-guess local IP (local_ip_v4); no external address resolution. Use only when the host has a public IP on its primary interface.
auto-natResolved via NAT-PMP or UPnP at startup (see section 17.9.5). If no NAT-PMP or UPnP gateway responds, the value is left unset and the profile may fail to start.
stun:stun.example.comSTUN binding request sent to the named server at startup; the reflexive address returned by the server is used. An optional port may be appended: stun:stun.example.com:3478. FreeSWITCH retries the lookup up to 5 times before giving up.
host:fs.example.comDNS A-record lookup for the given hostname performed at startup; the resolved address is used. Suitable for dynamic DNS setups.

All resolution forms (auto-nat, stun:, host:) are performed once when the profile loads or is restarted. The resolved address is stored statically for the lifetime of the profile. Changes to the external IP (for example, after a DHCP renewal) are not detected automatically; reload the profile to refresh.

0.0.0.0 is explicitly rejected. FreeSWITCH logs a warning and substitutes the local best-guess IP.

vars.xml Global Variables and the external Profile

The vanilla configuration centralizes external IP resolution in conf/vars.xml using the stun-set preprocessor command:

<!-- conf/vars.xml -->
<X-PRE-PROCESS cmd="stun-set" data="external_rtp_ip=stun:stun.freeswitch.org"/>
<X-PRE-PROCESS cmd="stun-set" data="external_sip_ip=stun:stun.freeswitch.org"/>

stun-set performs the STUN lookup at configuration load time and stores the result in the named variable. The bundled external profile then references these variables:

<!-- conf/sip_profiles/external.xml -->
<param name="rtp-ip" value="$${local_ip_v4}"/>
<param name="sip-ip" value="$${local_ip_v4}"/>
<param name="ext-rtp-ip" value="$${external_rtp_ip}"/>
<param name="ext-sip-ip" value="$${external_sip_ip}"/>

Replace the stun:stun.freeswitch.org values with a literal IP, a host: form, or your own STUN server. For production deployments with a stable public IP, a literal address is preferred over STUN to eliminate dependency on an external server at startup.

If external_rtp_ip or external_sip_ip are unset (for example, STUN resolution failed), the external profile falls back to the resolved local_ip_v4 value, which will be a private address behind NAT.

auto-nat: NAT-PMP and UPnP Discovery

Setting ext-sip-ip or ext-rtp-ip to auto-nat causes FreeSWITCH to query the local gateway using NAT-PMP (RFC 6886) and UPnP IGD to obtain the public IP address. FreeSWITCH tries NAT-PMP first; if no response is received, it falls back to UPnP.

When a gateway is found, FreeSWITCH stores the public address in the global variables nat_public_addr, nat_private_addr, and nat_type (pmp or upnp). A background thread listens for UPnP SSDP announcements and refreshes the address if the gateway signals an IP change.

auto-nat is appropriate only when FreeSWITCH is directly behind a residential or small-office NAT gateway that supports NAT-PMP or UPnP and the gateway is on the same broadcast segment. It is not suitable for cloud instances (no compliant gateway to query) or enterprise networks where NAT-PMP and UPnP are disabled by policy. Use a literal IP, stun:, or host: in those environments.

If auto-nat is used and no gateway responds, ext-sip-ip and ext-rtp-ip are left unresolved and the profile will not advertise a public address.

Far-End NAT Handling

FreeSWITCH also needs to handle SIP endpoints that are themselves behind NAT: their REGISTER Contact headers contain private addresses that FreeSWITCH cannot route to, and their RTP source port may differ from what their SDP advertises.

aggressive-nat-detection

When aggressive-nat-detection is true, FreeSWITCH inspects the Via header of every inbound REGISTER and INVITE. If any of the following conditions hold, the request is treated as originating from behind NAT:

  • The Via received parameter is present (indicates the sending IP differed from the Via host).
  • The Via host does not match the actual source IP of the packet.
  • The Via port does not match the actual source port of the packet.

When NAT is detected, FreeSWITCH stores the actual source IP and port and uses them for routing outbound requests to that endpoint, overriding the Contact address.

Default: false. Enabled by default on the external profile in the vanilla configuration (commented out but available).

apply-nat-acl

apply-nat-acl names an ACL defined in conf/acl.conf.xml. Any endpoint whose Contact IP falls within the named ACL is treated as being behind NAT regardless of the Via headers. This is useful when client IP ranges are known but those clients do not set Via headers that trigger aggressive-nat-detection.

The parameter can be specified multiple times to name additional ACLs. Setting the value to none clears all previously added ACL entries. FreeSWITCH will not add an ACL whose IP range includes the profile's own sip-ip address; it logs an error and skips that entry.

NDLB-force-rport

NDLB-force-rport controls rport handling for inbound and outbound SIP messages. The accepted values and their behavior are:

Valueserver_rport_levelclient_rport_levelEffect
true21Respond to inbound requests using the source port (rport) rather than the Via port. Add rport to outbound requests.
safe31Same as true for outbound; applies rport response selectively for inbound based on User-Agent heuristics (Polycom, KIRK).
client-onlyunchanged1Add rport to outbound requests only; do not force rport on inbound responses.
server-only10Apply rport to inbound responses only; do not add rport to outbound requests.
disabled00Disable rport entirely.

Default (profile startup): server_rport_level=1, client_rport_level=1, equivalent to server-only.

NDLB-force-rport with value true is the most common setting for profiles that accept registrations from endpoints behind NAT. It ensures FreeSWITCH sends responses back to the port from which the request arrived rather than the port listed in the Via header, which behind NAT is often wrong.

NDLB-received-in-nat-reg-contact

When true, FreeSWITCH appends a ;received=IP:port parameter to the Contact URI stored in the registration database whenever the registrant is detected as NAT. This allows the stored contact to reflect the actual public-facing address. Default: false.

RTP Auto-Adjust (far-end media NAT)

When an endpoint is behind NAT, the source IP and port of arriving RTP packets often differ from the address in the SDP c=/m= lines. FreeSWITCH's RTP auto-adjust mechanism detects this: after a configurable number of consecutive packets arrive from the same alternate source, FreeSWITCH updates the remote RTP destination to that observed address.

This is enabled by default. The profile parameter disable-rtp-auto-adjust (section 17.7) turns it off globally; the per-call channel variable disable_rtp_auto_adjust=true turns it off for a single call. Auto-adjust is automatically suppressed for AVPF (WebRTC) sessions, where ICE handles address negotiation.

NAT Traversal Parameter Reference

The following table consolidates all NAT-related profile parameters.

ParameterPurposeAccepted ValuesDefault
ext-sip-ipPublic IP advertised in SIP Contact, Via, and Record-RouteLiteral IP, auto, auto-nat, stun:server, host:namenone
ext-rtp-ipPublic IP advertised in SDP c= and m= linesLiteral IP, auto, auto-nat, stun:server, host:namenone
sip-ipLocal IP to bind the SIP stack toLiteral IP, auto, interface:nameauto
rtp-ipLocal IP to bind RTP sockets toLiteral IP, auto, interface:nameauto
aggressive-nat-detectionDetect NAT by comparing Via headers to actual packet sourcetrue, falsefalse
apply-nat-aclTreat Contact IPs in this ACL as being behind NATACL name (repeatable), nonenone
NDLB-force-rportControl rport behavior for inbound and outbound SIPtrue, safe, client-only, server-only, disabledserver-only (level 1)
NDLB-received-in-nat-reg-contactAppend actual source address to stored registration Contact when NAT is detectedtrue, falsefalse
disable-rtp-auto-adjustDisable automatic update of the remote RTP address from observed packet sourcetrue, falsefalse
local-network-aclACL defining the local network; endpoints on this ACL are excluded from NAT detectionACL name, nonelocalnet.auto

WebRTC note: Browser-based WebRTC clients use ICE for NAT traversal and do not rely on the ext-sip-ip/ext-rtp-ip mechanism. For ICE, STUN, and TURN configuration for WebRTC endpoints, see Chapter 9: WebRTC with Verto and Chapter 10: WebRTC over SIP (WSS).