diff --git a/CHANGES b/CHANGES index ad16a4008ffdf9012033b0e2afc86c04cbb10b74..d843848cfb3494f928b1cbc6cd47852a56dd8051 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 0000000000000000000000000000000000000000..8831e20023e5f5d2aa476c6f67ff3c563c3cdff1 --- /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 7a7af0b4e021b320747cc2d3ae44f9e32b5c36a0..e095a96309a3dbf9e5b1a30946f780917b9ca90c 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 ('/') </para></description> </configOption> + <configOption name="srv_lookups" default="yes"> + <synopsis>Perform SRV lookups for provided hostnames.</synopsis> + <description><para>When enabled, <replaceable>srv_lookups</replaceable> 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. + </para></description> + </configOption> <configOption name="type"> <synopsis>Must be of type 'identify'.</synopsis> </configOption> @@ -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");