;rtp_timeout_hold= ; Hang up channel if RTP is not received for the specified
; number of seconds when the channel is on hold (default:
; "0" or not enabled)
+;rtp_port_start= ; Per-endpoint starting RTP port number, overriding the
+ ; global rtp.conf setting. Must be used together with
+ ; rtp_port_end. (default: "0" or use global range)
+;rtp_port_end= ; Per-endpoint ending RTP port number, overriding the
+ ; global rtp.conf setting. Must be used together with
+ ; rtp_port_start. (default: "0" or use global range)
;contact_user= ; On outgoing requests, force the user portion of the Contact
; header to this value (default: "")
;incoming_call_offer_pref= ; Based on this setting, a joint list of
--- /dev/null
+"""add rtp_port_start and rtp_port_end to ps_endpoints
+
+Revision ID: e89e30cee53f
+Revises: bb6d54e22913
+Create Date: 2026-04-02 10:00:00.000000
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'e89e30cee53f'
+down_revision = 'bb6d54e22913'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('ps_endpoints',
+ sa.Column('rtp_port_start', sa.Integer))
+ op.add_column('ps_endpoints',
+ sa.Column('rtp_port_end', sa.Integer))
+
+
+def downgrade():
+ op.drop_column('ps_endpoints', 'rtp_port_end')
+ op.drop_column('ps_endpoints', 'rtp_port_start')
unsigned int follow_early_media_fork;
/*! Accept updated SDPs on non-100rel 18X and 2XX responses with the same To tag */
unsigned int accept_multiple_sdp_answers;
+ /*! Per-endpoint RTP port range start (0 means use global rtp.conf setting) */
+ unsigned int port_start;
+ /*! Per-endpoint RTP port range end (0 means use global rtp.conf setting) */
+ unsigned int port_end;
};
/*!
struct ast_sched_context *sched, const struct ast_sockaddr *sa,
void *data);
+/*!
+ * \brief Options for creating a new RTP instance
+ *
+ * This structure allows passing additional options when creating an
+ * RTP instance via \ref ast_rtp_instance_new_with_options. New fields
+ * can be added in the future without changing the function signature.
+ *
+ * \since 20.20.0
+ * \since 22.10.0
+ * \since 23.4.0
+ */
+struct ast_rtp_instance_options {
+ /*! Starting port number for this instance (0 to use global) */
+ unsigned int port_start;
+ /*! Ending port number for this instance (0 to use global) */
+ unsigned int port_end;
+};
+
+/*!
+ * \brief Create a new RTP instance with additional options
+ *
+ * \param engine_name Name of the engine to use for the RTP instance
+ * \param sched Scheduler context that the RTP engine may want to use
+ * \param sa Address we want to bind to
+ * \param data Unique data for the engine
+ * \param options Pointer to options struct, or NULL to use defaults
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ *
+ * Example usage:
+ *
+ * \code
+ * struct ast_rtp_instance_options options = { .port_start = 15000, .port_end = 15010 };
+ * struct ast_rtp_instance *instance = NULL;
+ * instance = ast_rtp_instance_new_with_options(NULL, sched, &sin, NULL, &options);
+ * \endcode
+ *
+ * This creates a new RTP instance using the specified options. When a
+ * per-instance port range is provided the global rtp.conf range is ignored.
+ * If options is NULL or port values are both 0 the global range is used.
+ *
+ * \note The per-instance port range overrides the global rtp.conf settings
+ * for this specific RTP instance only. It does not need to be a
+ * subset of the global range.
+ *
+ * \since 20.20.0
+ * \since 22.10.0
+ * \since 23.4.0
+ */
+struct ast_rtp_instance *ast_rtp_instance_new_with_options(const char *engine_name,
+ struct ast_sched_context *sched, const struct ast_sockaddr *sa,
+ void *data, const struct ast_rtp_instance_options *options);
+
/*!
* \brief Destroy an RTP instance
*
*/
int ast_rtp_instance_get_keepalive(struct ast_rtp_instance *instance);
+/*!
+ * \brief Get the per-instance RTP port range start
+ *
+ * \param instance The RTP instance
+ *
+ * \return port start value (0 means use global setting)
+ *
+ * \since 20.20.0
+ * \since 22.10.0
+ * \since 23.4.0
+ */
+unsigned int ast_rtp_instance_get_port_start(struct ast_rtp_instance *instance);
+
+/*!
+ * \brief Get the per-instance RTP port range end
+ *
+ * \param instance The RTP instance
+ *
+ * \return port end value (0 means use global setting)
+ *
+ * \since 20.20.0
+ * \since 22.10.0
+ * \since 23.4.0
+ */
+unsigned int ast_rtp_instance_get_port_end(struct ast_rtp_instance *instance);
+
/*!
* \brief Get the RTP engine in use on an RTP instance
*
AST_VECTOR(, int) extmap_negotiated;
/*! Negotiated RTP extensions (using index based on unique id) */
AST_VECTOR(, struct rtp_extmap) extmap_unique_ids;
+ /*! Per-instance RTP port range start (0 means use global) */
+ unsigned int rtp_port_start;
+ /*! Per-instance RTP port range end (0 means use global) */
+ unsigned int rtp_port_end;
};
/*!
struct ast_rtp_instance *ast_rtp_instance_new(const char *engine_name,
struct ast_sched_context *sched, const struct ast_sockaddr *sa,
void *data)
+{
+ return ast_rtp_instance_new_with_options(
+ engine_name, sched, sa, data, NULL);
+}
+
+struct ast_rtp_instance *ast_rtp_instance_new_with_options(const char *engine_name,
+ struct ast_sched_context *sched, const struct ast_sockaddr *sa,
+ void *data, const struct ast_rtp_instance_options *options)
{
struct ast_sockaddr address = {{0,}};
struct ast_rtp_instance *instance = NULL;
struct ast_rtp_engine *engine = NULL;
struct ast_module *mod_ref;
+ unsigned int port_start = options ? options->port_start : 0;
+ unsigned int port_end = options ? options->port_end : 0;
AST_RWLIST_RDLOCK(&engines);
ast_sockaddr_copy(&instance->local_address, sa);
ast_sockaddr_copy(&address, sa);
+ /* Set the per-instance port range before the engine allocates the transport */
+ instance->rtp_port_start = port_start;
+ instance->rtp_port_end = port_end;
+
if (ast_rtp_codecs_payloads_initialize(&instance->codecs)) {
ao2_ref(instance, -1);
return NULL;
return NULL;
}
- ast_debug(1, "Using engine '%s' for RTP instance '%p'\n", engine->name, instance);
+ if (port_start && port_end) {
+ ast_debug(1, "Using engine '%s' for RTP instance '%p' with port range %d-%d\n",
+ engine->name, instance, port_start, port_end);
+ } else {
+ ast_debug(1, "Using engine '%s' for RTP instance '%p'\n", engine->name, instance);
+ }
/*
* And pass it off to the engine to setup
return instance->keepalive;
}
+unsigned int ast_rtp_instance_get_port_start(struct ast_rtp_instance *instance)
+{
+ return instance->rtp_port_start;
+}
+
+unsigned int ast_rtp_instance_get_port_end(struct ast_rtp_instance *instance)
+{
+ return instance->rtp_port_end;
+}
+
struct ast_rtp_engine *ast_rtp_instance_get_engine(struct ast_rtp_instance *instance)
{
return instance->engine;
channel is hung up. By default this option is set to 0, which means do not check.
</para></description>
</configOption>
+ <configOption name="rtp_port_start" default="0">
+ <since>
+ <version>20.20.0</version>
+ <version>22.10.0</version>
+ <version>23.4.0</version>
+ </since>
+ <synopsis>Per-endpoint starting RTP port number.</synopsis>
+ <description><para>
+ Defines the starting port number for a dedicated per-endpoint RTP port
+ range, overriding the global rtp.conf setting. Must be used together
+ with <literal>rtp_port_end</literal>. When set to 0, the global
+ rtp.conf range is used. The value must be between 1024 and 65535.
+ </para></description>
+ </configOption>
+ <configOption name="rtp_port_end" default="0">
+ <since>
+ <version>20.20.0</version>
+ <version>22.10.0</version>
+ <version>23.4.0</version>
+ </since>
+ <synopsis>Per-endpoint ending RTP port number.</synopsis>
+ <description><para>
+ Defines the ending port number for a dedicated per-endpoint RTP port
+ range, overriding the global rtp.conf setting. Must be used together
+ with <literal>rtp_port_start</literal>. When set to 0, the global
+ rtp.conf range is used. The value must be greater than
+ <literal>rtp_port_start</literal> and between 1024 and 65535.
+ </para></description>
+ </configOption>
<configOption name="acl">
<since>
<version>13.10.0</version>
return -1;
}
+ if (endpoint->media.rtp.port_start || endpoint->media.rtp.port_end) {
+ if (!endpoint->media.rtp.port_start || !endpoint->media.rtp.port_end) {
+ ast_log(LOG_ERROR, "Endpoint '%s': Both rtp_port_start and rtp_port_end must be set together\n",
+ ast_sorcery_object_get_id(endpoint));
+ return -1;
+ }
+ if (endpoint->media.rtp.port_start < 1024 || endpoint->media.rtp.port_end < 1024) {
+ ast_log(LOG_ERROR, "Endpoint '%s': rtp_port_start and rtp_port_end must be at least 1024\n",
+ ast_sorcery_object_get_id(endpoint));
+ return -1;
+ }
+ if (endpoint->media.rtp.port_end <= endpoint->media.rtp.port_start) {
+ ast_log(LOG_ERROR, "Endpoint '%s': rtp_port_end (%u) must be greater than rtp_port_start (%u)\n",
+ ast_sorcery_object_get_id(endpoint),
+ endpoint->media.rtp.port_end,
+ endpoint->media.rtp.port_start);
+ return -1;
+ }
+ ast_debug(1, "Endpoint '%s': Using per-endpoint RTP port range %u-%u\n",
+ ast_sorcery_object_get_id(endpoint),
+ endpoint->media.rtp.port_start,
+ endpoint->media.rtp.port_end);
+ }
+
if (endpoint->preferred_codec_only) {
if (endpoint->media.incoming_call_offer_pref.flags != (AST_SIP_CALL_CODEC_PREF_LOCAL | AST_SIP_CALL_CODEC_PREF_INTERSECT | AST_SIP_CALL_CODEC_PREF_ALL)) {
ast_log(LOG_ERROR, "Setting both preferred_codec_only and incoming_call_offer_pref is not supported on endpoint '%s'\n",
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_keepalive", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.rtp.keepalive));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_timeout", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.rtp.timeout));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_timeout_hold", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.rtp.timeout_hold));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_port_start", "0", OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_endpoint, media.rtp.port_start), 0, 65535);
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_port_end", "0", OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_endpoint, media.rtp.port_end), 0, 65535);
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "one_touch_recording", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, info.recording.enabled));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "inband_progress", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, inband_progress));
ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "call_group", "", group_handler, callgroup_to_str, NULL, 0, 0);
<parameter name="RtpTimeoutHold">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='rtp_timeout_hold']/synopsis/node())"/></para>
</parameter>
+ <parameter name="RtpPortStart">
+ <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='rtp_port_start']/synopsis/node())"/></para>
+ </parameter>
+ <parameter name="RtpPortEnd">
+ <para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='rtp_port_end']/synopsis/node())"/></para>
+ </parameter>
<parameter name="SecurityNegotiation">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='endpoint']/configOption[@name='security_negotiation']/synopsis/node())"/></para>
</parameter>
}
}
- if (!(session_media->rtp = ast_rtp_instance_new(session->endpoint->media.rtp.engine, sched, media_address, NULL))) {
- ast_log(LOG_ERROR, "Unable to create RTP instance using RTP engine '%s'\n", session->endpoint->media.rtp.engine);
- return -1;
+ if (session->endpoint->media.rtp.port_start && session->endpoint->media.rtp.port_end) {
+ struct ast_rtp_instance_options options = {
+ .port_start = session->endpoint->media.rtp.port_start,
+ .port_end = session->endpoint->media.rtp.port_end,
+ };
+ if (!(session_media->rtp = ast_rtp_instance_new_with_options(
+ session->endpoint->media.rtp.engine, sched, media_address, NULL,
+ &options))) {
+ ast_log(LOG_ERROR, "Unable to create RTP instance using RTP engine '%s' with port range %u-%u\n",
+ session->endpoint->media.rtp.engine,
+ session->endpoint->media.rtp.port_start,
+ session->endpoint->media.rtp.port_end);
+ return -1;
+ }
+ } else {
+ if (!(session_media->rtp = ast_rtp_instance_new(session->endpoint->media.rtp.engine, sched, media_address, NULL))) {
+ ast_log(LOG_ERROR, "Unable to create RTP instance using RTP engine '%s'\n", session->endpoint->media.rtp.engine);
+ return -1;
+ }
}
ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_NAT, session->endpoint->media.rtp.symmetric);
static int rtp_allocate_transport(struct ast_rtp_instance *instance, struct ast_rtp *rtp)
{
int x, startplace, i, maxloops;
+ unsigned int port_start, port_end;
rtp->strict_rtp_state = (strictrtp ? STRICT_RTP_CLOSED : STRICT_RTP_OPEN);
+ /* Determine the port range to use: per-instance override or global */
+ port_start = ast_rtp_instance_get_port_start(instance);
+ port_end = ast_rtp_instance_get_port_end(instance);
+ if (port_start > 0 && port_end > 0 && port_end > port_start) {
+ ast_debug_rtp(1, "(%p) RTP using per-instance port range %d-%d\n",
+ instance, port_start, port_end);
+ } else {
+ port_start = rtpstart;
+ port_end = rtpend;
+ }
+
/* Create a new socket for us to listen on and use */
if ((rtp->s = create_new_socket("RTP", &rtp->bind_address)) < 0) {
ast_log(LOG_WARNING, "Failed to create a new socket for RTP instance '%p'\n", instance);
}
/* Now actually find a free RTP port to use */
- x = (ast_random() % (rtpend - rtpstart)) + rtpstart;
+ x = (ast_random() % (port_end - port_start)) + port_start;
x = x & ~1;
startplace = x;
/* Protection against infinite loops in the case there is a potential case where the loop is not broken such as an odd
start port sneaking in (even though this condition is checked at load.) */
- maxloops = rtpend - rtpstart;
+ maxloops = port_end - port_start;
for (i = 0; i <= maxloops; i++) {
ast_sockaddr_set_port(&rtp->bind_address, x);
/* Try to bind, this will tell us whether the port is available or not */
}
x += 2;
- if (x > rtpend) {
- x = (rtpstart + 1) & ~1;
+ if (x > port_end) {
+ x = (port_start + 1) & ~1;
}
/* See if we ran out of ports or if the bind actually failed because of something other than the address being in use */