res_pjsip_endpoint_identifier_ip: Add support for SRV lookups.
authorJoshua Colp <jcolp@digium.com>
Thu, 5 Jan 2017 12:11:43 +0000 (12:11 +0000)
committerJoshua Colp <jcolp@digium.com>
Fri, 6 Jan 2017 15:00:22 +0000 (09:00 -0600)
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
contrib/ast-db-manage/config/versions/28ab27a7826d_add_srv_lookups_to_identify.py [new file with mode: 0644]
res/res_pjsip_endpoint_identifier_ip.c

diff --git a/CHANGES b/CHANGES
index ad16a40..d843848 100644 (file)
--- 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 (file)
index 0000000..8831e20
--- /dev/null
@@ -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')
index 7a7af0b..e095a96 100644 (file)
                                                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");