From fd14d7302f0c1fa748bdaa0d878f9377c0e60735 Mon Sep 17 00:00:00 2001 From: George Joseph Date: Sun, 25 Aug 2019 20:20:13 -0600 Subject: [PATCH] res_pjsip_nat: Restore original contact for REGISTER responses RFC3261 Section 10 "Registrations", specifically paragraph "10.2.4: Refreshing Bindings", states that a user agent compares each contact address (in a 200 REGISTER response) to see if it created the contact. If the Asterisk endpoint has the rewrite_contact option set however, the contact host and port sent back in the 200 response will be the rewritten one and not the one sent by the user agent. This prevents the user agent from matching its own contact. Some user agents get very upset when this happens and will not consider the registration successful. While this is rare, it is acceptable behavior especially if more than 1 user agent is allowed to register to a single endpoint/aor. This commit updates res_pjsip_nat (where rewrite_contact is implemented) to store the original incoming Contact header in a new "x-ast-orig-host" URI parameter before rewriting it, and to restore the original host and port to the Contact headers in the outgoing response. This is only done if the request is a REGISTER and rewrite_contact is enabled. pjsip_message_filter was also updated to ensure that if a request comes in with any existing x-ast-* URI parameters, we remove them so they don't conflict. Asterisk will never send a request with those headers in it but someone might just decide to add them to a request they craft and send to Asterisk. NOTE: If a device changes its contact address and registers again, it's a NEW registration. If the device didn't unregister the original registration then all existing behavior based on aor/remove_existing and aor/max_contacts apply. ASTERISK-28502 Reported-by: Ross Beer Change-Id: Idc263ad2d2d7bd8faa047e5804d96a5fe1cd282e --- res/res_pjsip/pjsip_message_filter.c | 40 ++++++++++++- res/res_pjsip_nat.c | 84 +++++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/res/res_pjsip/pjsip_message_filter.c b/res/res_pjsip/pjsip_message_filter.c index b70985bd1a..1826ee2030 100644 --- a/res/res_pjsip/pjsip_message_filter.c +++ b/res/res_pjsip/pjsip_message_filter.c @@ -400,6 +400,40 @@ static void print_uri_debug(enum uri_type ut, pjsip_rx_data *rdata, pjsip_hdr *h #endif } +/*! + * /internal + * + * We want to make sure that any incoming requests don't already + * have x-ast-* parameters in any URIs or we may get confused + * if symmetric transport (x-ast-txp) or rewrite_contact (x-ast-orig-host) + * is used later on. + */ +static void remove_x_ast_params(pjsip_uri *header_uri){ + pjsip_sip_uri *uri; + pjsip_param *param; + + if (!header_uri) { + return; + } + + uri = pjsip_uri_get_uri(header_uri); + if (!uri) { + return; + } + + param = uri->other_param.next; + + while (param != &uri->other_param) { + /* We need to save off 'next' because pj_list_erase will remove it */ + pjsip_param *next = param->next; + + if (pj_strncmp2(¶m->name, "x-ast-", 6) == 0) { + pj_list_erase(param); + } + param = next; + } +} + static pj_bool_t on_rx_process_uris(pjsip_rx_data *rdata) { pjsip_contact_hdr *contact = NULL; @@ -414,6 +448,7 @@ static pj_bool_t on_rx_process_uris(pjsip_rx_data *rdata) PJSIP_SC_UNSUPPORTED_URI_SCHEME, NULL, NULL, NULL); return PJ_TRUE; } + remove_x_ast_params(rdata->msg_info.msg->line.req.uri); if (!is_sip_uri(rdata->msg_info.from->uri)) { print_uri_debug(URI_TYPE_FROM, rdata, (pjsip_hdr *)rdata->msg_info.from); @@ -421,6 +456,7 @@ static pj_bool_t on_rx_process_uris(pjsip_rx_data *rdata) PJSIP_SC_UNSUPPORTED_URI_SCHEME, NULL, NULL, NULL); return PJ_TRUE; } + remove_x_ast_params(rdata->msg_info.from->uri); if (!is_sip_uri(rdata->msg_info.to->uri)) { print_uri_debug(URI_TYPE_TO, rdata, (pjsip_hdr *)rdata->msg_info.to); @@ -428,7 +464,7 @@ static pj_bool_t on_rx_process_uris(pjsip_rx_data *rdata) PJSIP_SC_UNSUPPORTED_URI_SCHEME, NULL, NULL, NULL); return PJ_TRUE; } - + remove_x_ast_params(rdata->msg_info.to->uri); contact = (pjsip_contact_hdr *) pjsip_msg_find_hdr( rdata->msg_info.msg, PJSIP_H_CONTACT, NULL); @@ -448,6 +484,8 @@ static pj_bool_t on_rx_process_uris(pjsip_rx_data *rdata) PJSIP_SC_UNSUPPORTED_URI_SCHEME, NULL, NULL, NULL); return PJ_TRUE; } + remove_x_ast_params(contact->uri); + contact = (pjsip_contact_hdr *) pjsip_msg_find_hdr( rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next); } diff --git a/res/res_pjsip_nat.c b/res/res_pjsip_nat.c index bbf33597c2..dc7d10b6da 100644 --- a/res/res_pjsip_nat.c +++ b/res/res_pjsip_nat.c @@ -32,8 +32,43 @@ #include "asterisk/module.h" #include "asterisk/acl.h" +/*! URI parameter for original host/port */ +#define AST_SIP_X_AST_ORIG_HOST "x-ast-orig-host" +#define AST_SIP_X_AST_ORIG_HOST_LEN 15 + +static void save_orig_contact_host(pjsip_rx_data *rdata, pjsip_sip_uri *uri) +{ + pjsip_param *x_orig_host; + pj_str_t p_value; +#define COLON_LEN 1 +#define MAX_PORT_LEN 5 + + if (rdata->msg_info.msg->type != PJSIP_REQUEST_MSG || + rdata->msg_info.msg->line.req.method.id != PJSIP_REGISTER_METHOD) { + return; + } + + ast_debug(1, "Saving contact '%.*s:%d'\n", + (int)uri->host.slen, uri->host.ptr, uri->port); + + x_orig_host = PJ_POOL_ALLOC_T(rdata->tp_info.pool, pjsip_param); + x_orig_host->name = pj_strdup3(rdata->tp_info.pool, AST_SIP_X_AST_ORIG_HOST); + p_value.slen = pj_strlen(&uri->host) + COLON_LEN + MAX_PORT_LEN; + p_value.ptr = (char*)pj_pool_alloc(rdata->tp_info.pool, p_value.slen + 1); + p_value.slen = snprintf(p_value.ptr, p_value.slen + 1, "%.*s:%d", (int)uri->host.slen, uri->host.ptr, uri->port); + pj_strassign(&x_orig_host->value, &p_value); + pj_list_insert_before(&uri->other_param, x_orig_host); + + return; +} + static void rewrite_uri(pjsip_rx_data *rdata, pjsip_sip_uri *uri) { + + if (pj_strcmp2(&uri->host, rdata->pkt_info.src_name) != 0) { + save_orig_contact_host(rdata, uri); + } + pj_cstr(&uri->host, rdata->pkt_info.src_name); uri->port = rdata->pkt_info.src_port; if (!strcasecmp("WSS", rdata->tp_info.transport->type_name)) { @@ -264,7 +299,44 @@ static int nat_invoke_hook(void *obj, void *arg, int flags) return 0; } -static pj_status_t nat_on_tx_message(pjsip_tx_data *tdata) +static void restore_orig_contact_host(pjsip_tx_data *tdata) +{ + pjsip_contact_hdr *contact; + + if (tdata->msg->type != PJSIP_RESPONSE_MSG) { + return; + } + + contact = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL); + while (contact) { + pjsip_sip_uri *contact_uri = pjsip_uri_get_uri(contact->uri); + pj_str_t x_name = { AST_SIP_X_AST_ORIG_HOST, AST_SIP_X_AST_ORIG_HOST_LEN }; + pjsip_param *x_orig_host = pjsip_param_find(&contact_uri->other_param, &x_name); + + if (x_orig_host) { + char host_port[x_orig_host->value.slen + 1]; + char *sep; + + ast_debug(1, "Restoring contact %.*s:%d to %.*s\n", (int)contact_uri->host.slen, + contact_uri->host.ptr, contact_uri->port, + (int)x_orig_host->value.slen, x_orig_host->value.ptr); + + strncpy(host_port, x_orig_host->value.ptr, x_orig_host->value.slen); + host_port[x_orig_host->value.slen] = '\0'; + sep = strchr(host_port, ':'); + if (sep) { + *sep = '\0'; + sep++; + pj_strdup2(tdata->pool, &contact_uri->host, host_port); + contact_uri->port = strtol(sep, NULL, 10); + } + pj_list_erase(x_orig_host); + } + contact = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, contact->next); + } +} + +static pj_status_t process_nat(pjsip_tx_data *tdata) { RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup); RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup); @@ -364,6 +436,16 @@ static pj_status_t nat_on_tx_message(pjsip_tx_data *tdata) return PJ_SUCCESS; } +static pj_status_t nat_on_tx_message(pjsip_tx_data *tdata) { + pj_status_t rc; + + rc = process_nat(tdata); + restore_orig_contact_host(tdata); + + return rc; +} + + static pjsip_module nat_module = { .name = { "NAT", 3 }, .id = -1,