res_pjsip_endpoint_identifier_ip: Add an option to match requests by header

This patch adds a new features to the endpoint identifier module,
'match_header'. When set, inbound requests are matched by a provided SIP
header: value pair. This option works in conjunction with the existing
'match' configuration option, such that if any 'match*' attribute
matches an inbound request, the request is associated with the specified
endpoint.

Since this module now identifies by more than just IP address,
appropriate renaming of the module and/or variables can be done in a
non-release branch.

ASTERISK-26863 #close

Change-Id: Icfc14835c962f92e35e67bbdb235cf0589de5453
pull/8/head
Matt Jordan 8 years ago
parent d5e42fefec
commit 30f52d79d7

@ -12,6 +12,14 @@
--- Functionality changes from Asterisk 14.3.0 to Asterisk 14.4.0 ------------
------------------------------------------------------------------------------
AMI
------------------
* The 'PJSIPShowEndpoint' command's respone event of 'IdentifyDetail' now
contains a new optional parameter, 'MatchHeader', mapping to the new
configuration option 'match_header' for the corresponding 'identify' object.
It should be noted that since 'match_header' takes in a key: value pair, the
event parameter will contain a ':' as well.
app_record
------------------
* Added new 'u' option to Record() application which prevents Asterisk from
@ -41,6 +49,20 @@ res_pjsip_transport_websocket
when Asterisk attempts to send SIP requests to do something like initiate
call hangup.
res_pjsip_endpoint_identifier_ip
------------------
* A new option has been added to the 'identify' configuration object,
'match_header'. The 'match_header' attribute should contain a SIP
header: value pair that, When set, will cause inbound requests that contain
the matching SIP header/value pair to be associated with the corresponding
endpoint. This option is cumulative with the 'match' option, so that if
either option matches the request, the request is associated with the
endpoint.
In a future release, this module will be renamed to something more
appropriate, as it now matches inbound requests on more than just IP
address.
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 14.2.0 to Asterisk 14.3.0 ------------
------------------------------------------------------------------------------

@ -0,0 +1,21 @@
"""Add match_header attribute to identify
Revision ID: 465e70e8c337
Revises: 28ab27a7826d
Create Date: 2017-03-14 08:13:53.986681
"""
# revision identifiers, used by Alembic.
revision = '465e70e8c337'
down_revision = '28ab27a7826d'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('ps_endpoint_id_ips', sa.Column('match_header', sa.String(255)))
def downgrade():
op.drop_column('ps_endpoint_id_ips', 'match_header')

@ -35,20 +35,33 @@
/*** DOCUMENTATION
<configInfo name="res_pjsip_endpoint_identifier_ip" language="en_US">
<synopsis>Module that identifies endpoints via source IP address</synopsis>
<synopsis>Module that identifies endpoints</synopsis>
<configFile name="pjsip.conf">
<configObject name="identify">
<synopsis>Identifies endpoints via source IP address</synopsis>
<synopsis>Identifies endpoints via some criteria.</synopsis>
<description>
<para>This module provides alternatives to matching inbound requests to
a configured endpoint. At least one of the matching mechanisms
must be provided, or the object configuration will be invalid.</para>
<para>If multiple criteria are provided, an inbound request will
be matched if it matches <emphasis>any</emphasis> of the criteria.</para>
<para>The matching mechanisms are provided by the following
configuration options:</para>
<enumlist>
<enum name="match"><para>Match by source IP address.</para></enum>
<enum name="match_header"><para>Match by SIP header.</para></enum>
</enumlist>
</description>
<configOption name="endpoint">
<synopsis>Name of Endpoint</synopsis>
</configOption>
<configOption name="match">
<synopsis>IP addresses or networks to match against</synopsis>
<synopsis>IP addresses or networks to match against.</synopsis>
<description><para>
The value is a comma-delimited list of IP addresses. IP addresses may
have a subnet mask appended. The subnet mask may be written in either
CIDR or dot-decimal notation. Separate the IP address and subnet
mask with a slash ('/')
mask with a slash ('/').
</para></description>
</configOption>
<configOption name="srv_lookups" default="yes">
@ -58,6 +71,14 @@
hostnames to determine additional addresses that traffic may originate from.
</para></description>
</configOption>
<configOption name="match_header">
<synopsis>Header/value pair to match against.</synopsis>
<description><para>A SIP header who value is used to match against. SIP
requests containing the header, along with the specified value, will be
mapped to the specified endpoint. The header must be specified with a
<literal>:</literal>, as in <literal>match_header = SIPHeader: value</literal>.
</para></description>
</configOption>
<configOption name="type">
<synopsis>Must be of type 'identify'.</synopsis>
</configOption>
@ -77,6 +98,8 @@ struct ip_identify_match {
AST_DECLARE_STRING_FIELDS(
/*! The name of the endpoint */
AST_STRING_FIELD(endpoint_name);
/*! If matching by header, the header/value to match against */
AST_STRING_FIELD(match_header);
);
/*! \brief Networks or addresses that should match this */
struct ast_ha *matches;
@ -109,7 +132,48 @@ static void *ip_identify_alloc(const char *name)
return identify;
}
/*! \brief Comparator function for a matching object */
/*! \brief Comparator function for matching an object by header */
static int header_identify_match_check(void *obj, void *arg, int flags)
{
struct ip_identify_match *identify = obj;
struct pjsip_rx_data *rdata = arg;
pjsip_generic_string_hdr *header;
pj_str_t pj_header_name;
pj_str_t pj_header_value;
char *c_header = ast_strdupa(identify->match_header);
char *c_value;
c_value = strchr(c_header, ':');
if (!c_value) {
ast_log(LOG_WARNING, "Identify '%s' has invalid header_match: No ':' separator found!\n",
ast_sorcery_object_get_id(identify));
return 0;
}
*c_value = '\0';
c_value++;
c_value = ast_strip(c_value);
pj_header_name = pj_str(c_header);
header = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &pj_header_name, NULL);
if (!header) {
ast_debug(3, "SIP message does not contain header '%s'\n", c_header);
return 0;
}
pj_header_value = pj_str(c_value);
if (pj_strcmp(&pj_header_value, &header->hvalue)) {
ast_debug(3, "SIP message contains header '%s' but value '%.*s' does not match value '%s' for endpoint '%s'\n",
c_header,
(int) pj_strlen(&header->hvalue), pj_strbuf(&header->hvalue),
c_value,
identify->endpoint_name);
return 0;
}
return CMP_MATCH | CMP_STOP;
}
/*! \brief Comparator function for matching an object by IP address */
static int ip_identify_match_check(void *obj, void *arg, int flags)
{
struct ip_identify_match *identify = obj;
@ -147,10 +211,14 @@ static struct ast_sip_endpoint *ip_identify(pjsip_rx_data *rdata)
ast_sockaddr_parse(&addr, rdata->pkt_info.src_name, PARSE_PORT_FORBID);
ast_sockaddr_set_port(&addr, rdata->pkt_info.src_port);
if (!(match = ao2_callback(candidates, 0, ip_identify_match_check, &addr))) {
ast_debug(3, "'%s' did not match any identify section rules\n",
match = ao2_callback(candidates, 0, ip_identify_match_check, &addr);
if (!match) {
ast_debug(3, "Identify checks by IP address failed to find match: '%s' did not match any identify section rules\n",
ast_sockaddr_stringify(&addr));
return NULL;
match = ao2_callback(candidates, 0, header_identify_match_check, rdata);
if (!match) {
return NULL;
}
}
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", match->endpoint_name);
@ -495,7 +563,7 @@ static int cli_print_header(void *obj, void *arg, int flags)
filler = CLI_LAST_TABSTOP - indent - 24;
ast_str_append(&context->output_buffer, 0,
"%*s: <ip/cidr%*.*s>\n",
"%*s: <criteria%*.*s>\n",
indent, "Match", filler, filler, CLI_HEADER_FILLER);
context->indent_level--;
@ -532,6 +600,13 @@ static int cli_print_body(void *obj, void *arg, int flags)
addr, ast_sockaddr_cidr_bits(&match->netmask));
}
if (!ast_strlen_zero(ident->match_header)) {
ast_str_append(&context->output_buffer, 0, "%*s: %s\n",
indent,
"Match",
ident->match_header);
}
context->indent_level--;
if (context->indent_level == 0) {
@ -592,6 +667,7 @@ static int load_module(void)
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "type", "", OPT_NOOP_T, 0, 0);
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "endpoint", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, endpoint_name));
ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "identify", "match", "", ip_identify_match_handler, match_to_str, match_to_var_list, 0, 0);
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "match_header", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ip_identify_match, match_header));
ast_sorcery_object_field_register(ast_sip_get_sorcery(), "identify", "srv_lookups", "yes", OPT_BOOL_T, 1, FLDSET(struct ip_identify_match, srv_lookups));
ast_sorcery_load_object(ast_sip_get_sorcery(), "identify");

Loading…
Cancel
Save