From e45da15315e4da1b4f73606f58929b54ff4cd857 Mon Sep 17 00:00:00 2001 From: Alexis Chenard Date: Mon, 1 Jun 2026 20:38:28 +0000 Subject: [PATCH] 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'. --- configs/samples/pjsip.conf.sample | 8 ++++++++ ...2ace275_add_external_signaling_hostname.py | 20 +++++++++++++++++++ include/asterisk/res_pjsip.h | 2 ++ res/res_pjsip/config_transport.c | 10 ++++++++++ res/res_pjsip/pjsip_config.xml | 19 ++++++++++++++++++ res/res_pjsip/pjsip_manager.xml | 3 +++ res/res_pjsip_nat.c | 11 +++++++--- 7 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 contrib/ast-db-manage/config/versions/2285f2ace275_add_external_signaling_hostname.py diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index 3d346789d4..e01f3f756c 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -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") diff --git a/contrib/ast-db-manage/config/versions/2285f2ace275_add_external_signaling_hostname.py b/contrib/ast-db-manage/config/versions/2285f2ace275_add_external_signaling_hostname.py new file mode 100644 index 0000000000..106beea152 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/2285f2ace275_add_external_signaling_hostname.py @@ -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') \ No newline at end of file diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 34e0bb9d4c..61cb1ce041 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -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; diff --git a/res/res_pjsip/config_transport.c b/res/res_pjsip/config_transport.c index 850dd21b5e..5139b75662 100644 --- a/res/res_pjsip/config_transport.c +++ b/res/res_pjsip/config_transport.c @@ -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); diff --git a/res/res_pjsip/pjsip_config.xml b/res/res_pjsip/pjsip_config.xml index b2ef0bddfb..a0049be36c 100644 --- a/res/res_pjsip/pjsip_config.xml +++ b/res/res_pjsip/pjsip_config.xml @@ -2448,6 +2448,25 @@ External port for SIP signalling + + + 20.21.0 + 22.11.0 + 23.5.0 + + Value to use in SIP Contact and Via headers as hostname + + 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 + external_signaling_address and both cannot + be set simultaneously. If not set, existing behavior using + external_signaling_address is preserved. + + 12.2.0 diff --git a/res/res_pjsip/pjsip_manager.xml b/res/res_pjsip/pjsip_manager.xml index 548b974332..cdf36b66f3 100644 --- a/res/res_pjsip/pjsip_manager.xml +++ b/res/res_pjsip/pjsip_manager.xml @@ -223,6 +223,9 @@ + + + diff --git a/res/res_pjsip_nat.c b/res/res_pjsip_nat.c index f4e63a61b4..a7689dee60 100644 --- a/res/res_pjsip_nat.c +++ b/res/res_pjsip_nat.c @@ -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; }