summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES7
-rw-r--r--channels/chan_sip.c74
-rw-r--r--funcs/func_srv.c254
-rw-r--r--include/asterisk/srv.h35
-rw-r--r--main/srv.c35
5 files changed, 387 insertions, 18 deletions
diff --git a/CHANGES b/CHANGES
index 8e1e96259..2ea3e5ff7 100644
--- 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
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index f2308fabe..bd6cb1889 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -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
index 000000000..339a0284b
--- /dev/null
+++ b/funcs/func_srv.c
@@ -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");
diff --git a/include/asterisk/srv.h b/include/asterisk/srv.h
index a3d2c7a6e..d98a1d0b9 100644
--- a/include/asterisk/srv.h
+++ b/include/asterisk/srv.h
@@ -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 */
diff --git a/main/srv.c b/main/srv.c
index 3be8bbd00..c65065033 100644
--- a/main/srv.c
+++ b/main/srv.c
@@ -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;
+}