From a7d856cd96eb1e1f02fcde6b3e6bef35e1715bd5 Mon Sep 17 00:00:00 2001 From: Joshua Colp Date: Thu, 5 Jan 2017 12:11:43 +0000 Subject: [PATCH] res_pjsip_endpoint_identifier_ip: Add support for SRV lookups. This change implements SRV support for the IP based endpoint identifier module. All possible addresses through SRV are looked up and added as matches. If no SRV records are available a fallback to normal host resolution is done. If an IP address is provided then no SRV lookup occurs. This is configured using the "srv_lookups" option on the identify section and defaults to "yes". ASTERISK-26693 Change-Id: I6b641e275bf96629320efa8b479737062aed82ac --- CHANGES | 6 + ...8ab27a7826d_add_srv_lookups_to_identify.py | 31 +++++ res/res_pjsip_endpoint_identifier_ip.c | 116 +++++++++++++++--- 3 files changed, 133 insertions(+), 20 deletions(-) create mode 100644 contrib/ast-db-manage/config/versions/28ab27a7826d_add_srv_lookups_to_identify.py diff --git a/CHANGES b/CHANGES index ad16a4008f..d843848cfb 100644 --- a/CHANGES +++ b/CHANGES @@ -110,6 +110,12 @@ res_pjsip ID, AuthenticateQualify, OutboundProxy, Path, QualifyFrequency and QualifyTimeout. Existing fields have not been disturbed. +res_pjsip_endpoint_identifier_ip +------------------ + * SRV lookups can now be done on provided hostnames to determine additional + source IP addresses for requests. This is configurable using the + "srv_lookups" option on the identify and defaults to "yes". + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 14.1.0 to Asterisk 14.2.0 ------------ ------------------------------------------------------------------------------ diff --git a/contrib/ast-db-manage/config/versions/28ab27a7826d_add_srv_lookups_to_identify.py b/contrib/ast-db-manage/config/versions/28ab27a7826d_add_srv_lookups_to_identify.py new file mode 100644 index 0000000000..8831e20023 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/28ab27a7826d_add_srv_lookups_to_identify.py @@ -0,0 +1,31 @@ +"""add srv_lookups to identify + +Revision ID: 28ab27a7826d +Revises: 4468b4a91372 +Create Date: 2017-01-06 14:53:38.829655 + +""" + +# revision identifiers, used by Alembic. +revision = '28ab27a7826d' +down_revision = '4468b4a91372' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import ENUM + +YESNO_NAME = 'yesno_values' +YESNO_VALUES = ['yes', 'no'] + +def upgrade(): + ############################# Enums ############################## + + # yesno_values have already been created, so use postgres enum object + # type to get around "already created" issue - works okay with mysql + yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False) + + op.add_column('ps_endpoint_id_ips', sa.Column('srv_lookups', yesno_values)) + + +def downgrade(): + op.drop_column('ps_endpoint_id_ips', 'srv_lookups') diff --git a/res/res_pjsip_endpoint_identifier_ip.c b/res/res_pjsip_endpoint_identifier_ip.c index 7a7af0b4e0..e095a96309 100644 --- a/res/res_pjsip_endpoint_identifier_ip.c +++ b/res/res_pjsip_endpoint_identifier_ip.c @@ -51,6 +51,13 @@ mask with a slash ('/') + + Perform SRV lookups for provided hostnames. + When enabled, srv_lookups will + perform SRV lookups for _sip._udp, _sip._tcp, and _sips._tcp of the given + hostnames to determine additional addresses that traffic may originate from. + + Must be of type 'identify'. @@ -70,6 +77,8 @@ struct ip_identify_match { ); /*! \brief Networks or addresses that should match this */ struct ast_ha *matches; + /*! \brief Perform SRV resolution of hostnames */ + unsigned int srv_lookups; }; /*! \brief Destructor function for a matching object */ @@ -153,6 +162,72 @@ static struct ast_sip_endpoint_identifier ip_identifier = { .identify_endpoint = ip_identify, }; +/*! \brief Helper function which performs a host lookup and adds result to identify match */ +static int ip_identify_match_host_lookup(struct ip_identify_match *identify, const char *host) +{ + struct ast_sockaddr *addrs; + int num_addrs = 0, error = 0, i; + int results = 0; + + num_addrs = ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC); + if (!num_addrs) { + return -1; + } + + for (i = 0; i < num_addrs; ++i) { + /* Check if the address is already in the list, if so don't bother adding it again */ + if (identify->matches && (ast_apply_ha(identify->matches, &addrs[i]) != AST_SENSE_ALLOW)) { + continue; + } + + /* We deny what we actually want to match because there is an implicit permit all rule for ACLs */ + identify->matches = ast_append_ha("d", ast_sockaddr_stringify_addr(&addrs[i]), identify->matches, &error); + + if (!identify->matches || error) { + results = -1; + break; + } + + results += 1; + } + + ast_free(addrs); + + return results; +} + +/*! \brief Helper function which performs an SRV lookup and then resolves the hostname */ +static int ip_identify_match_srv_lookup(struct ip_identify_match *identify, const char *prefix, const char *host) +{ + char service[NI_MAXHOST]; + struct srv_context *context = NULL; + int srv_ret; + const char *srvhost; + unsigned short srvport; + int results = 0; + + snprintf(service, sizeof(service), "%s.%s", prefix, host); + + while (!(srv_ret = ast_srv_lookup(&context, service, &srvhost, &srvport))) { + int hosts; + + /* In the case of the SRV lookup we don't care if it fails, we will output a log message + * when we fallback to a normal lookup. + */ + hosts = ip_identify_match_host_lookup(identify, srvhost); + if (hosts == -1) { + results = -1; + break; + } else { + results += hosts; + } + } + + ast_srv_cleanup(&context); + + return results; +} + /*! \brief Custom handler for match field */ static int ip_identify_match_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { @@ -165,9 +240,9 @@ static int ip_identify_match_handler(const struct aco_option *opt, struct ast_va } while ((current_string = ast_strip(strsep(&input_string, ",")))) { - struct ast_sockaddr *addrs; - int num_addrs = 0, error = 0, i; char *mask = strrchr(current_string, '/'); + struct ast_sockaddr address; + int error, results = 0; if (ast_strlen_zero(current_string)) { continue; @@ -185,28 +260,28 @@ static int ip_identify_match_handler(const struct aco_option *opt, struct ast_va continue; } - num_addrs = ast_sockaddr_resolve(&addrs, current_string, PARSE_PORT_FORBID, AST_AF_UNSPEC); - if (!num_addrs) { - ast_log(LOG_ERROR, "Address '%s' provided on ip endpoint identifier '%s' did not resolve to any address\n", - var->value, ast_sorcery_object_get_id(obj)); - return -1; - } - - for (i = 0; i < num_addrs; ++i) { - /* We deny what we actually want to match because there is an implicit permit all rule for ACLs */ - identify->matches = ast_append_ha("d", ast_sockaddr_stringify_addr(&addrs[i]), identify->matches, &error); - - if (!identify->matches || error) { - ast_log(LOG_ERROR, "Failed to add address '%s' to ip endpoint identifier '%s'\n", - ast_sockaddr_stringify_addr(&addrs[i]), ast_sorcery_object_get_id(obj)); - error = -1; - break; + /* If the provided string is not an IP address perform SRV resolution on it */ + if (identify->srv_lookups && !ast_sockaddr_parse(&address, current_string, 0)) { + results = ip_identify_match_srv_lookup(identify, "_sip._udp", current_string); + if (results != -1) { + results += ip_identify_match_srv_lookup(identify, "_sip._tcp", current_string); + } + if (results != -1) { + results += ip_identify_match_srv_lookup(identify, "_sips._tcp", current_string); } } - ast_free(addrs); + /* If SRV falls fall back to a normal lookup on the host itself */ + if (!results) { + results = ip_identify_match_host_lookup(identify, current_string); + } - if (error) { + if (results == 0) { + ast_log(LOG_ERROR, "Address '%s' provided on ip endpoint identifier '%s' did not resolve to any address\n", + current_string, ast_sorcery_object_get_id(obj)); + } else if (results == -1) { + ast_log(LOG_ERROR, "An error occurred when adding resolution results of '%s' on '%s'\n", + current_string, ast_sorcery_object_get_id(obj)); return -1; } } @@ -469,6 +544,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", "srv_lookups", "yes", OPT_BOOL_T, 1, FLDSET(struct ip_identify_match, srv_lookups)); ast_sorcery_load_object(ast_sip_get_sorcery(), "identify"); ast_sip_register_endpoint_identifier_with_name(&ip_identifier, "ip");