func_srv and explicit specification of a remote IP for SIP.
authorMark Michelson <mmichelson@digium.com>
Fri, 9 Apr 2010 14:37:50 +0000 (14:37 +0000)
committerMark Michelson <mmichelson@digium.com>
Fri, 9 Apr 2010 14:37:50 +0000 (14:37 +0000)
From Review Board:
There are two interrelated changes here.

First, there is the introduction of func_srv. This adds two new read-only
dialplan functions, SRVQUERY and SRVRESULT. They work very similarly to the
ENUMQUERY and ENUMRESULT functions, except that this allows one to query SRV
records instead. In order to facilitate this work, I added a couple of new API
calls to srv.h. ast_srv_get_record_count tells the number of records returned
by an SRV lookup. This number is calculated at the time of the SRV lookup.
ast_srv_get_nth_record allows one to get a numbered SRV record.

Second, there is the modification to chan_sip that allows one to specify a
hostname or IP address (along with a port) to send an outgoing INVITE to when
dialing a SIP peer. This goes hand-in-hand with func_srv. You can query SRV
records and then use the host and port from the results to dial via a specific
host instead of what is configured in sip.conf.

Review: https://reviewboard.asterisk.org/r/608
SWP-1200

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@256485 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CHANGES
channels/chan_sip.c
funcs/func_srv.c [new file with mode: 0644]
include/asterisk/srv.h
main/srv.c

diff --git a/CHANGES b/CHANGES
index 8e1e962..2ea3e5f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -56,6 +56,9 @@ SIP Changes
  * Added 'use_q850_reason' configuration option for generating and parsing
    if available  Reason: Q.850;cause=<cause code> header. It is implemented
    in some gateways for better passing PRI/SS7 cause codes via SIP.
+ * When dialing SIP peers, a new component may be added to the end of the dialstring
+   to indicate that a specific remote IP address or host should be used when dialing
+   the particular peer. The dialstring format is SIP/peer/exten/host_or_IP.
 
 IAX2 Changes
 -----------
@@ -146,6 +149,10 @@ Applications
 
 Dialplan Functions
 ------------------
+ * SRVQUERY and SRVRESULT functions added. This can be used to query and iterate
+   over SRV records associated with a specific service. From the CLI, type
+   'core show function SRVQUERY' and 'core show function SRVRESULT' for more
+   details on how these may be used.
  * PITCH_SHIFT dialplan function added. This function can be used to modify the
    pitch of a channel's tx and rx audio streams.
  * Added new dialplan functions CONNECTEDLINE and REDIRECTING which permits
index f2308fa..bd6cb18 100644 (file)
@@ -1260,7 +1260,7 @@ static int respprep(struct sip_request *resp, struct sip_pvt *p, const char *msg
 static const struct sockaddr_in *sip_real_dst(const struct sip_pvt *p);
 static void build_via(struct sip_pvt *p);
 static int create_addr_from_peer(struct sip_pvt *r, struct sip_peer *peer);
-static int create_addr(struct sip_pvt *dialog, const char *opeer, struct sockaddr_in *sin, int newdialog);
+static int create_addr(struct sip_pvt *dialog, const char *opeer, struct sockaddr_in *sin, int newdialog, struct sockaddr_in *remote_address);
 static char *generate_random_string(char *buf, size_t size);
 static void build_callid_pvt(struct sip_pvt *pvt);
 static void build_callid_registry(struct sip_registry *reg, struct in_addr ourip, const char *fromdomain);
@@ -3998,7 +3998,7 @@ static int create_addr_from_peer(struct sip_pvt *dialog, struct sip_peer *peer)
 /*! \brief create address structure from device name
  *      Or, if peer not found, find it in the global DNS
  *      returns TRUE (-1) on failure, FALSE on success */
-static int create_addr(struct sip_pvt *dialog, const char *opeer, struct sockaddr_in *sin, int newdialog)
+static int create_addr(struct sip_pvt *dialog, const char *opeer, struct sockaddr_in *sin, int newdialog, struct sockaddr_in *remote_address)
 {
        struct hostent *hp;
        struct ast_hostent ahp;
@@ -4026,7 +4026,9 @@ static int create_addr(struct sip_pvt *dialog, const char *opeer, struct sockadd
                        set_socket_transport(&dialog->socket, 0);
                }
                res = create_addr_from_peer(dialog, peer);
-               if (!ast_strlen_zero(port)) {
+               if (remote_address && remote_address->sin_addr.s_addr) {
+                       dialog->sa = dialog->recv = *remote_address;
+               } else if (!ast_strlen_zero(port)) {
                        if ((portno = atoi(port))) {
                                dialog->sa.sin_port = dialog->recv.sin_port = htons(portno);
                        }
@@ -9859,7 +9861,7 @@ static int __sip_subscribe_mwi_do(struct sip_subscription_mwi *mwi)
        }
        
        /* Setup the destination of our subscription */
-       if (create_addr(mwi->call, mwi->hostname, &mwi->us, 0)) {
+       if (create_addr(mwi->call, mwi->hostname, &mwi->us, 0, NULL)) {
                dialog_unlink_all(mwi->call, TRUE, TRUE);
                mwi->call = dialog_unref(mwi->call, "unref dialog after unlink_all");
                return 0;
@@ -10267,7 +10269,7 @@ static int manager_sipnotify(struct mansession *s, const struct message *m)
                return 0;
        }
 
-       if (create_addr(p, channame, NULL, 0)) {
+       if (create_addr(p, channame, NULL, 0, NULL)) {
                /* Maybe they're not registered, etc. */
                dialog_unlink_all(p, TRUE, TRUE);
                dialog_unref(p, "unref dialog inside for loop" );
@@ -10570,7 +10572,7 @@ static int transmit_register(struct sip_registry *r, int sipmethod, const char *
                        r->us.sin_port = htons(r->portno);
 
                /* Find address to hostname */
-               if (create_addr(p, r->hostname, &r->us, 0)) {
+               if (create_addr(p, r->hostname, &r->us, 0, NULL)) {
                        /* we have what we hope is a temporary network error,
                         * probably DNS.  We need to reschedule a registration try */
                        dialog_unlink_all(p, TRUE, TRUE);
@@ -15947,7 +15949,7 @@ static char *sip_cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_arg
                        return CLI_FAILURE;
                }
 
-               if (create_addr(p, a->argv[i], NULL, 1)) {
+               if (create_addr(p, a->argv[i], NULL, 1, NULL)) {
                        /* Maybe they're not registered, etc. */
                        dialog_unlink_all(p, TRUE, TRUE);
                        dialog_unref(p, "unref dialog inside for loop" );
@@ -22330,12 +22332,19 @@ static struct ast_channel *sip_request_call(const char *type, format_t format, c
        char tmp[256];
        char *dest = data;
        char *dnid;
-       char *secret = NULL;
-       char *md5secret = NULL;
-       char *authname = NULL;
+       char *secret = NULL;
+       char *md5secret = NULL;
+       char *authname = NULL;
        char *trans = NULL;
+       char *remote_address;
        enum sip_transport transport = 0;
+       struct sockaddr_in remote_address_sin = { .sin_family = AF_INET };
        format_t oldformat = format;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(peerorhost);
+               AST_APP_ARG(exten);
+               AST_APP_ARG(remote_address);
+       );
 
        /* mask request with some set of allowed formats.
         * XXX this needs to be fixed.
@@ -22372,7 +22381,6 @@ static struct ast_channel *sip_request_call(const char *type, format_t format, c
        /* Save the destination, the SIP dial string */
        ast_copy_string(tmp, dest, sizeof(tmp));
 
-
        /* Find DNID and take it away */
        dnid = strchr(tmp, '!');
        if (dnid != NULL) {
@@ -22380,11 +22388,14 @@ static struct ast_channel *sip_request_call(const char *type, format_t format, c
                ast_string_field_set(p, todnid, dnid);
        }
 
+       /* Divvy up the items separated by slashes */
+       AST_NONSTANDARD_APP_ARGS(args, tmp, '/');
+
        /* Find at sign - @ */
-       host = strchr(tmp, '@');
+       host = strchr(args.peerorhost, '@');
        if (host) {
                *host++ = '\0';
-               ext = tmp;
+               ext = args.peerorhost;
                secret = strchr(ext, ':');
        }
        if (secret) {
@@ -22415,10 +22426,37 @@ static struct ast_channel *sip_request_call(const char *type, format_t format, c
        }
 
        if (!host) {
-               ext = strchr(tmp, '/');
-               if (ext)
-                       *ext++ = '\0';
-               host = tmp;
+               ext = args.exten;
+               host = args.peerorhost;
+               remote_address = args.remote_address;
+       } else {
+               remote_address = args.remote_address;
+               if (!ast_strlen_zero(args.exten)) {
+                       ast_log(LOG_NOTICE, "Conflicting extension values given. Using '%s' and not '%s'\n", ext, args.exten);
+               }
+       }
+
+       if (!ast_strlen_zero(remote_address)) {
+               struct hostent *hp;
+               struct ast_hostent ahp;
+               char *port;
+               unsigned short port_num = transport & SIP_TRANSPORT_TLS ? STANDARD_TLS_PORT : STANDARD_SIP_PORT;
+
+               port = strchr(remote_address, ':');
+               if (port) {
+                       *port++ = '\0';
+                       if (sscanf(port, "%hu", &port_num) != 1) {
+                               ast_log(LOG_WARNING, "Invalid port number provided in remote address. Using %hu\n", port_num);
+                       }
+               }
+
+               hp = ast_gethostbyname(remote_address, &ahp);
+               if (!hp) {
+                       ast_log(LOG_WARNING, "Unable to find IP address for host %s. We will not use this remote IP address\n", remote_address);
+               } else {
+                       memcpy(&remote_address_sin.sin_addr, hp->h_addr, sizeof(remote_address_sin.sin_addr));
+                       remote_address_sin.sin_port = htons(port_num);
+               }
        }
 
        set_socket_transport(&p->socket, transport);
@@ -22428,7 +22466,7 @@ static struct ast_channel *sip_request_call(const char *type, format_t format, c
                ext = extension (user part of URI)
                dnid = destination of the call (applies to the To: header)
        */
-       if (create_addr(p, host, NULL, 1)) {
+       if (create_addr(p, host, NULL, 1, &remote_address_sin)) {
                *cause = AST_CAUSE_UNREGISTERED;
                ast_debug(3, "Cant create SIP call - target device not registered\n");
                dialog_unlink_all(p, TRUE, TRUE);
diff --git a/funcs/func_srv.c b/funcs/func_srv.c
new file mode 100644 (file)
index 0000000..339a028
--- /dev/null
@@ -0,0 +1,254 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 1999 - 2006 Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief SRV Functions
+ *
+ * \author Mark Michelson <mmichelson@digium.com>
+ *
+ * \ingroup functions
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/module.h"
+#include "asterisk/srv.h"
+#include "asterisk/pbx.h"
+#include "asterisk/app.h"
+
+/*** DOCUMENTATION
+       <function name="SRVQUERY" language="en_US">
+               <synopsis>
+                       Initiate an SRV query.
+               </synopsis>
+               <syntax>
+                       <parameter name="service" required="true">
+                               <para>The service for which to look up SRV records. An example would be something
+                               like <literal>_sip._udp.example.com</literal></para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This will do an SRV lookup of the given service.</para>
+               </description>
+       </function>
+       <function name="SRVRESULT" language="en_US">
+               <synopsis>
+                       Retrieve results from an SRVQUERY.
+               </synopsis>
+               <syntax>
+                       <parameter name="id" required="true">
+                               <para>The identifier returned by the SRVQUERY function.</para>
+                       </parameter>
+                       <parameter name="resultnum" required="true">
+                               <para>The number of the result that you want to retrieve.</para>
+                               <para>Results start at <literal>1</literal>. If this argument is specified
+                               as <literal>getnum</literal>, then it will return the total number of results
+                               that are available.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>This function will retrieve results from a previous use
+                       of the SRVQUERY function.</para>
+               </description>
+       </function>
+ ***/
+
+struct srv_result_datastore {
+       struct srv_context *context;
+       char id[1];
+};
+
+static void srds_destroy_cb(void *data)
+{
+       struct srv_result_datastore *datastore = data;
+       ast_srv_cleanup(&datastore->context);
+       ast_free(datastore);
+}
+
+static const struct ast_datastore_info srv_result_datastore_info = {
+       .type = "SRVQUERY",
+       .destroy = srds_destroy_cb,
+};
+
+static struct srv_context *srv_datastore_setup(const char *service, struct ast_channel *chan)
+{
+       struct srv_result_datastore *srds;
+       struct ast_datastore *datastore;
+       const char *host;
+       unsigned short port;
+
+       if (!(srds = ast_calloc(1, sizeof(*srds) + strlen(service)))) {
+               return NULL;
+       }
+
+       if (ast_srv_lookup(&srds->context, service, &host, &port) < 0) {
+               ast_log(LOG_NOTICE, "Error performing lookup of service '%s'\n", service);
+               ast_free(srds);
+               return NULL;
+       }
+
+       strcpy(srds->id, service);
+       
+       if (!(datastore = ast_datastore_alloc(&srv_result_datastore_info, srds->id))) {
+               ast_srv_cleanup(&srds->context);
+               ast_free(srds);
+               return NULL;
+       }
+
+       datastore->data = srds;
+       ast_channel_lock(chan);
+       ast_channel_datastore_add(chan, datastore);
+       ast_channel_unlock(chan);
+       return srds->context;
+}
+
+static int srv_query_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+       if (!chan) {
+               ast_log(LOG_WARNING, "%s cannot be used without a channel\n", cmd);
+               return -1;
+       }
+
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, "%s requires a service as an argument\n", cmd);
+               return -1;
+       }
+
+       if (!srv_datastore_setup(data, chan)) {
+               return -1;
+       }
+
+       ast_copy_string(buf, data, len);
+
+       return 0;
+}
+
+static struct ast_custom_function srv_query_function = {
+       .name = "SRVQUERY",
+       .read = srv_query_read,
+};
+
+static int srv_result_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+       struct srv_result_datastore *srds;
+       struct ast_datastore *datastore;
+       struct srv_context *srv_context;
+       char *parse;
+       const char *host;
+       unsigned short port, priority, weight;
+       unsigned int num;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(id);
+               AST_APP_ARG(resultnum);
+               AST_APP_ARG(field);
+       );
+
+       if (!chan) {
+               ast_log(LOG_WARNING, "%s cannot be used without a channel\n", cmd);
+               return -1;
+       }
+
+       if (ast_strlen_zero(data)) {
+               ast_log(LOG_WARNING, "%s requires two arguments (id and resultnum)\n", cmd);
+               return -1;
+       }
+
+       parse = ast_strdupa(data);
+
+       AST_STANDARD_APP_ARGS(args, parse);
+
+       ast_channel_lock(chan);
+       datastore = ast_channel_datastore_find(chan, &srv_result_datastore_info, args.id);
+       ast_channel_unlock(chan);
+
+       if (!datastore) {
+               /* They apparently decided to call SRVRESULT without first calling SRVQUERY.
+                * No problem, we'll do the SRV lookup now.
+                */
+               srv_context = srv_datastore_setup(args.id, chan);
+               if (!srv_context) {
+                       return -1;
+               }
+       } else {
+               srds = datastore->data;
+               srv_context = srds->context;
+       }
+
+       if (!strcasecmp(args.resultnum, "getnum")) {
+               snprintf(buf, len, "%u", ast_srv_get_record_count(srv_context));
+               return 0;
+       }
+
+       if (ast_strlen_zero(args.field)) {
+               ast_log(LOG_ERROR, "A field must be provided when requesting SRV data\n");
+               return -1;
+       }
+
+       if (sscanf(args.resultnum, "%30u", &num) != 1) {
+               ast_log(LOG_ERROR, "Invalid value '%s' for resultnum to %s\n", args.resultnum, cmd);
+               return -1;
+       }
+
+       if (ast_srv_get_nth_record(srv_context, num, &host, &port, &priority, &weight)) {
+               ast_log(LOG_ERROR, "Failed to get record number %u for %s\n", num, cmd);
+               return -1;
+       }
+
+       if (!strcasecmp(args.field, "host")) {
+               ast_copy_string(buf, host, len);
+       } else if (!strcasecmp(args.field, "port")) {
+               snprintf(buf, len, "%u", port);
+       } else if (!strcasecmp(args.field, "priority")) {
+               snprintf(buf, len, "%u", priority);
+       } else if (!strcasecmp(args.field, "weight")) {
+               snprintf(buf, len, "%u", weight);
+       } else {
+               ast_log(LOG_WARNING, "Unrecognized SRV field '%s'\n", args.field);
+               return -1;
+       }
+
+       return 0;
+}
+
+static struct ast_custom_function srv_result_function = {
+       .name = "SRVRESULT",
+       .read = srv_result_read,
+};
+
+static int unload_module(void)
+{
+       int res = 0;
+
+       res |= ast_custom_function_unregister(&srv_query_function);
+       res |= ast_custom_function_unregister(&srv_result_function);
+
+       return res;
+}
+
+static int load_module(void)
+{
+       int res = AST_MODULE_LOAD_SUCCESS;
+
+       res |= ast_custom_function_register(&srv_query_function);
+       res |= ast_custom_function_register(&srv_result_function);
+
+       return res;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "SRV related dialplan functions");
index a3d2c7a..d98a1d0 100644 (file)
@@ -61,4 +61,39 @@ void ast_srv_cleanup(struct srv_context **context);
 */
 extern int ast_get_srv(struct ast_channel *chan, char *host, int hostlen, int *port, const char *service);
 
+/*!
+ * \brief Get the number of records for a given SRV context
+ *
+ * \details
+ * This is meant to be used after calling ast_srv_lookup, so that
+ * one may retrieve the number of records returned during a specific
+ * SRV lookup.
+ *
+ * \param context The context returned by ast_srv_lookup
+ * \return Number of records in context
+ */
+unsigned int ast_srv_get_record_count(struct srv_context *context);
+
+/*!
+ * \brief Retrieve details from a specific SRV record
+ *
+ * \details
+ * After calling ast_srv_lookup, the srv_context will contain
+ * the data from several records. You can retrieve the data
+ * of a specific one by asking for a specific record number. The
+ * records are sorted based on priority and secondarily based on
+ * weight. See RFC 2782 for the exact sorting rules.
+ *
+ * \param context The context returned by ast_srv_lookup
+ * \param record_num The 1-indexed record number to retrieve
+ * \param[out] host The host portion of the record
+ * \param[out] port The port portion of the record
+ * \param[out] priority The priority portion of the record
+ * \param[out] weight The weight portion of the record
+ * \retval -1 Failed to retrieve information. Likely due to an out of
+ * range record_num
+ * \retval 0 Success
+ */
+int ast_srv_get_nth_record(struct srv_context *context, int record_num, const char **host,
+               unsigned short *port, unsigned short *priority, unsigned short *weight);
 #endif /* _ASTERISK_SRV_H */
index 3be8bbd..c650650 100644 (file)
@@ -65,6 +65,7 @@ struct srv_entry {
 struct srv_context {
        unsigned int have_weights:1;
        struct srv_entry *prev;
+       unsigned int num_records;
        AST_LIST_HEAD_NOLOCK(srv_entries, srv_entry) entries;
 };
 
@@ -221,6 +222,9 @@ int ast_srv_lookup(struct srv_context **context, const char *service, const char
                (*context)->prev = AST_LIST_FIRST(&(*context)->entries);
                *host = (*context)->prev->host;
                *port = (*context)->prev->port;
+               AST_LIST_TRAVERSE(&(*context)->entries, cur, list) {
+                       ++((*context)->num_records);
+               }
                return 0;
        }
 
@@ -286,3 +290,34 @@ int ast_get_srv(struct ast_channel *chan, char *host, int hostlen, int *port, co
 
        return ret;
 }
+
+unsigned int ast_srv_get_record_count(struct srv_context *context)
+{
+       return context->num_records;
+}
+
+int ast_srv_get_nth_record(struct srv_context *context, int record_num, const char **host,
+               unsigned short *port, unsigned short *priority, unsigned short *weight)
+{
+       int i = 1;
+       int res = -1;
+       struct srv_entry *entry;
+
+       if (record_num < 1 || record_num > context->num_records) {
+               return res;
+       }
+
+       AST_LIST_TRAVERSE(&context->entries, entry, list) {
+               if (i == record_num) {
+                       *host = entry->host;
+                       *port = entry->port;
+                       *priority = entry->priority;
+                       *weight = entry->weight;
+                       res = 0;
+                       break;
+               }
+               ++i;
+       }
+
+       return res;
+}