diff options
-rw-r--r-- | CHANGES | 7 | ||||
-rw-r--r-- | channels/chan_sip.c | 74 | ||||
-rw-r--r-- | funcs/func_srv.c | 254 | ||||
-rw-r--r-- | include/asterisk/srv.h | 35 | ||||
-rw-r--r-- | main/srv.c | 35 |
5 files changed, 387 insertions, 18 deletions
@@ -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; +} |