res_pjsip: Add external_signaling_hostname transport option

Adds a new transport option 'external_signaling_hostname' which allows
a hostname or FQDN to be used in SIP Contact and Via headers instead of
the automatically determined IP address. This is useful when a remote
SIP endpoint requires a fully qualified domain name in these headers.

The option is mutually exclusive with 'external_signaling_address' and
an error is raised at transport load time if both are set simultaneously.

Resolves: #1749

UserNote: A new pjsip.conf transport option 'external_signaling_hostname'
has been added. When set, this value will be used in SIP Contact and Via
headers instead of the automatically determined IP address. This option
is mutually exclusive with 'external_signaling_address'.
This commit is contained in:
Alexis Chenard
2026-06-01 20:38:28 +00:00
parent 8e3e3735ff
commit e45da15315
7 changed files with 70 additions and 3 deletions
+8
View File
@@ -1203,6 +1203,14 @@
; "")
;external_signaling_port=0 ; External port for SIP signalling (default:
; "0")
;external_signaling_hostname= ; Value to use in SIP Contact and Via headers
; as hostname instead of the automatically
; determined IP address. Useful when a remote
; SIP endpoint requires a fully qualified domain
; name rather than an IP address. No validation
; is performed on the provided value. Cannot be
; set together with external_signaling_address.
; (default: "")
;local_net= ; Network to consider local used for NAT purposes (default: "")
;password= ; Password required for transport (default: "")
;protocol=udp ; Protocol to use for SIP traffic (default: "udp")
@@ -0,0 +1,20 @@
"""Add external_signaling_hostname to ps_transports
Revision ID: 2285f2ace275
Revises: e89e30cee53f
Create Date: 2026-06-03
"""
# revision identifiers, used by Alembic.
revision = '2285f2ace275'
down_revision = 'e89e30cee53f'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('ps_transports', sa.Column('external_signaling_hostname', sa.String(40)))
def downgrade():
op.drop_column('ps_transports', 'external_signaling_hostname')
+2
View File
@@ -236,6 +236,8 @@ struct ast_sip_transport {
AST_STRING_FIELD(external_media_address);
/*! Optional domain to use for messages if provided could not be found */
AST_STRING_FIELD(domain);
/*! Optional FQDN to use in SIP Contact and Via headers instead of external_signaling_address */
AST_STRING_FIELD(external_signaling_hostname);
);
/*! Type of transport */
enum ast_transport type;
+10
View File
@@ -675,6 +675,15 @@ static int transport_apply(const struct ast_sorcery *sorcery, void *obj)
RAII_VAR(struct ast_variable *, changes, NULL, ast_variables_destroy);
pj_status_t res = -1;
int i;
/* Ensure external_signaling_address and external_signaling_hostname are mutually exclusive */
if (!ast_strlen_zero(transport->external_signaling_address) &&
!ast_strlen_zero(transport->external_signaling_hostname)) {
ast_log(LOG_ERROR, "Transport '%s' has both 'external_signaling_address' and "
"'external_signaling_hostname' set. Only one may be configured at a time.\n",
transport_id);
return -1;
}
#define BIND_TRIES 3
#define BIND_DELAY_US 100000
@@ -1856,6 +1865,7 @@ int ast_sip_initialize_sorcery_transport(void)
ast_sorcery_object_field_register(sorcery, "transport", "password", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, password));
ast_sorcery_object_field_register(sorcery, "transport", "external_signaling_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, external_signaling_address));
ast_sorcery_object_field_register(sorcery, "transport", "external_signaling_port", "0", OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_transport, external_signaling_port), 0, 65535);
ast_sorcery_object_field_register(sorcery, "transport", "external_signaling_hostname", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, external_signaling_hostname));
ast_sorcery_object_field_register(sorcery, "transport", "external_media_address", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, external_media_address));
ast_sorcery_object_field_register(sorcery, "transport", "domain", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_transport, domain));
ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_server", "", transport_tls_bool_handler, verify_server_to_str, NULL, 0, 0);
+19
View File
@@ -2448,6 +2448,25 @@
</since>
<synopsis>External port for SIP signalling</synopsis>
</configOption>
<configOption name="external_signaling_hostname">
<since>
<version>20.21.0</version>
<version>22.11.0</version>
<version>23.5.0</version>
</since>
<synopsis>Value to use in SIP Contact and Via headers as hostname</synopsis>
<description><para>
When set, this value is used in the SIP Contact and Via
headers of outgoing messages instead of the automatically
determined IP address. This is useful when a remote SIP
endpoint requires a fully qualified domain name in these
headers rather than an IP address. No validation is performed
on the provided value. This option is mutually exclusive with
<literal>external_signaling_address</literal> and both cannot
be set simultaneously. If not set, existing behavior using
<literal>external_signaling_address</literal> is preserved.
</para></description>
</configOption>
<configOption name="method">
<since>
<version>12.2.0</version>
+3
View File
@@ -223,6 +223,9 @@
<parameter name="ExternalSignalingPort">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='transport']/configOption[@name='external_signaling_port']/synopsis/node())"/></para>
</parameter>
<parameter name="ExternalSignalingHostname">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='transport']/configOption[@name='external_signaling_hostname']/synopsis/node())"/></para>
</parameter>
<parameter name="ExternalMediaAddress">
<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='transport']/configOption[@name='external_media_address']/synopsis/node())"/></para>
</parameter>
+8 -3
View File
@@ -32,6 +32,7 @@
#include "asterisk/res_pjsip_session.h"
#include "asterisk/module.h"
#include "asterisk/acl.h"
#include "asterisk/strings.h"
/*! URI parameter for original host/port */
#define AST_SIP_X_AST_ORIG_HOST "x-ast-orig-host"
@@ -370,8 +371,12 @@ static pj_status_t process_nat(pjsip_tx_data *tdata)
}
}
if (!ast_sockaddr_isnull(&transport_state->external_signaling_address)) {
if (!ast_sockaddr_isnull(&transport_state->external_signaling_address) ||
!ast_strlen_zero(transport->external_signaling_hostname)) {
pjsip_cseq_hdr *cseq = PJSIP_MSG_CSEQ_HDR(tdata->msg);
const char *signaling_host = !ast_strlen_zero(transport->external_signaling_hostname) ?
transport->external_signaling_hostname :
ast_sockaddr_stringify_host(&transport_state->external_signaling_address);
/* Update the Contact header with the external address. We only do this if
* a CSeq is not present (which should not happen - but we are extra safe),
@@ -387,7 +392,7 @@ static pj_status_t process_nat(pjsip_tx_data *tdata)
tdata->msg->line.status.code != PJSIP_SC_MOVED_TEMPORARILY )) {
/* We can only rewrite the URI when one is present */
if (uri || (uri = ast_sip_get_contact_sip_uri(tdata))) {
pj_strdup2(tdata->pool, &uri->host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
pj_strdup2(tdata->pool, &uri->host, signaling_host);
if (transport->external_signaling_port) {
uri->port = transport->external_signaling_port;
ast_debug(4, "Re-wrote Contact URI port to %d\n", uri->port);
@@ -397,7 +402,7 @@ static pj_status_t process_nat(pjsip_tx_data *tdata)
/* Update the via header if relevant */
if ((tdata->msg->type == PJSIP_REQUEST_MSG) && (via || (via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL)))) {
pj_strdup2(tdata->pool, &via->sent_by.host, ast_sockaddr_stringify_host(&transport_state->external_signaling_address));
pj_strdup2(tdata->pool, &via->sent_by.host, signaling_host);
if (transport->external_signaling_port) {
via->sent_by.port = transport->external_signaling_port;
}