Create iterative method for querying SRV results, and use that for finding AGI servers.

(closes issue #14775)
 Reported by: _brent_
 Patches: 
       20091215__issue14775.diff.txt uploaded by tilghman (license 14)
       hagi-5.patch uploaded by brent (license 388)
 Tested by: _brent_
 Reviewboard: https://reviewboard.asterisk.org/r/378/


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@241188 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Tilghman Lesher
2010-01-19 00:28:49 +00:00
parent 800313cbd9
commit 49bf540c71
4 changed files with 162 additions and 18 deletions

View File

@@ -1325,6 +1325,11 @@ AGI Changes
* If app_stack is loaded, GOSUB is a native AGI command that may be used to * If app_stack is loaded, GOSUB is a native AGI command that may be used to
invoke subroutines in the dialplan. Note that calling EXEC with Gosub invoke subroutines in the dialplan. Note that calling EXEC with Gosub
does not behave as expected; the native command needs to be used, instead. does not behave as expected; the native command needs to be used, instead.
* Added the ability to perform SRV lookups on fast AGI calls. To use this
feature, simply use hagi: instead of agi: as the protocol portion
of the URI parameter to the AGI function call in your dial plan. Also note
that specifying a port number in the AGI URI will disable SRV lookups,
even if you use the hagi: protocol.
Logger changes Logger changes
-------------- --------------

View File

@@ -31,6 +31,25 @@
no provisions for retrying or failover between records. no provisions for retrying or failover between records.
*/ */
/*!\brief An opaque type, for lookup usage */
struct srv_context;
/*!\brief Retrieve set of SRV lookups, in order
* \param[in] context A pointer in which to hold the result
* \param[in] service The service name to look up
* \param[out] host Result host
* \param[out] port Associated TCP portnum
* \retval -1 Query failed
* \retval 0 Result exists in host and port
* \retval 1 No more results
*/
extern int ast_srv_lookup(struct srv_context **context, const char *service, const char **host, unsigned short *port);
/*!\brief Cleanup resources associated with ast_srv_lookup
* \param context Pointer passed into ast_srv_lookup
*/
void ast_srv_cleanup(struct srv_context **context);
/*! Lookup entry in SRV records Returns 1 if found, 0 if not found, -1 on hangup /*! Lookup entry in SRV records Returns 1 if found, 0 if not found, -1 on hangup
Only do SRV record lookup if you get a domain without a port. If you get a port #, it's a DNS host name. Only do SRV record lookup if you get a domain without a port. If you get a port #, it's a DNS host name.
*/ */

View File

@@ -64,6 +64,7 @@ struct srv_entry {
struct srv_context { struct srv_context {
unsigned int have_weights:1; unsigned int have_weights:1;
struct srv_entry *prev;
AST_LIST_HEAD_NOLOCK(srv_entries, srv_entry) entries; AST_LIST_HEAD_NOLOCK(srv_entries, srv_entry) entries;
}; };
@@ -197,6 +198,55 @@ static void process_weights(struct srv_context *context)
AST_LIST_APPEND_LIST(&context->entries, &newlist, list); AST_LIST_APPEND_LIST(&context->entries, &newlist, list);
} }
int ast_srv_lookup(struct srv_context **context, const char *service, const char **host, unsigned short *port)
{
struct srv_entry *cur;
if (*context == NULL) {
if (!(*context = ast_calloc(1, sizeof(struct srv_context)))) {
return -1;
}
AST_LIST_HEAD_INIT_NOLOCK(&(*context)->entries);
if ((ast_search_dns(*context, service, C_IN, T_SRV, srv_callback)) < 0) {
ast_free(*context);
*context = NULL;
return -1;
}
if ((*context)->have_weights) {
process_weights(*context);
}
(*context)->prev = AST_LIST_FIRST(&(*context)->entries);
*host = (*context)->prev->host;
*port = (*context)->prev->port;
return 0;
}
if (((*context)->prev = AST_LIST_NEXT((*context)->prev, list))) {
/* Retrieve next item in result */
*host = (*context)->prev->host;
*port = (*context)->prev->port;
return 0;
} else {
/* No more results */
while ((cur = AST_LIST_REMOVE_HEAD(&(*context)->entries, list))) {
ast_free(cur);
}
ast_free(*context);
*context = NULL;
return 1;
}
}
void ast_srv_cleanup(struct srv_context **context)
{
const char *host;
unsigned short port;
while (!(ast_srv_lookup(context, NULL, &host, &port)));
}
int ast_get_srv(struct ast_channel *chan, char *host, int hostlen, int *port, const char *service) int ast_get_srv(struct ast_channel *chan, char *host, int hostlen, int *port, const char *service)
{ {
struct srv_context context = { .entries = AST_LIST_HEAD_NOLOCK_INIT_VALUE }; struct srv_context context = { .entries = AST_LIST_HEAD_NOLOCK_INIT_VALUE };

View File

@@ -60,6 +60,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/features.h" #include "asterisk/features.h"
#include "asterisk/term.h" #include "asterisk/term.h"
#include "asterisk/xmldoc.h" #include "asterisk/xmldoc.h"
#include "asterisk/srv.h"
#define AST_API_MODULE #define AST_API_MODULE
#include "asterisk/agi.h" #include "asterisk/agi.h"
@@ -897,6 +898,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#define MAX_CMD_LEN 80 #define MAX_CMD_LEN 80
#define AGI_NANDFS_RETRY 3 #define AGI_NANDFS_RETRY 3
#define AGI_BUF_LEN 2048 #define AGI_BUF_LEN 2048
#define SRV_PREFIX "_agi._tcp."
static char *app = "AGI"; static char *app = "AGI";
@@ -1339,32 +1341,28 @@ quit:
/* launch_netscript: The fastagi handler. /* launch_netscript: The fastagi handler.
FastAGI defaults to port 4573 */ FastAGI defaults to port 4573 */
static enum agi_result launch_netscript(char *agiurl, char *argv[], int *fds, int *efd, int *opid) static enum agi_result launch_netscript(char *agiurl, char *argv[], int *fds)
{ {
int s, flags, res, port = AGI_PORT; int s, flags, res, port = AGI_PORT;
struct pollfd pfds[1]; struct pollfd pfds[1];
char *host, *c, *script = ""; char *host, *c, *script;
struct sockaddr_in addr_in; struct sockaddr_in addr_in;
struct hostent *hp; struct hostent *hp;
struct ast_hostent ahp; struct ast_hostent ahp;
/* agiusl is "agi://host.domain[:port][/script/name]" */ /* agiurl is "agi://host.domain[:port][/script/name]" */
host = ast_strdupa(agiurl + 6); /* Remove agi:// */ host = ast_strdupa(agiurl + 6); /* Remove agi:// */
/* Strip off any script name */ /* Strip off any script name */
if ((c = strchr(host, '/'))) { if ((script = strchr(host, '/'))) {
*c = '\0'; *script++ = '\0';
c++; } else {
script = c; script = "";
} }
if ((c = strchr(host, ':'))) { if ((c = strchr(host, ':'))) {
*c = '\0'; *c++ = '\0';
c++;
port = atoi(c); port = atoi(c);
} }
if (efd) {
ast_log(LOG_WARNING, "AGI URI's don't support Enhanced AGI yet\n");
return -1;
}
if (!(hp = ast_gethostbyname(host, &ahp))) { if (!(hp = ast_gethostbyname(host, &ahp))) {
ast_log(LOG_WARNING, "Unable to locate host '%s'\n", host); ast_log(LOG_WARNING, "Unable to locate host '%s'\n", host);
return -1; return -1;
@@ -1423,20 +1421,92 @@ static enum agi_result launch_netscript(char *agiurl, char *argv[], int *fds, in
ast_debug(4, "Wow, connected!\n"); ast_debug(4, "Wow, connected!\n");
fds[0] = s; fds[0] = s;
fds[1] = s; fds[1] = s;
*opid = -1;
return AGI_RESULT_SUCCESS_FAST; return AGI_RESULT_SUCCESS_FAST;
} }
/*!
* \internal
* \brief The HA fastagi handler.
* \param agiurl The request URL as passed to Agi() in the dial plan
* \param argv The parameters after the URL passed to Agi() in the dial plan
* \param fds Input/output file descriptors
*
* Uses SRV lookups to try to connect to a list of FastAGI servers. The hostname in
* the URI is prefixed with _agi._tcp. prior to the DNS resolution. For
* example, if you specify the URI \a hagi://agi.example.com/foo.agi the DNS
* query would be for \a _agi._tcp.agi.example.com and you'll need to make sure
* this resolves.
*
* This function parses the URI, resolves the SRV service name, forms new URIs
* with the results of the DNS lookup, and then calls launch_netscript on the
* new URIs until one succeeds.
*
* \return the result of the AGI operation.
*/
static enum agi_result launch_ha_netscript(char *agiurl, char *argv[], int *fds)
{
char *host, *script;
enum agi_result result = AGI_RESULT_FAILURE;
struct srv_context *context = NULL;
int srv_ret;
char service[256];
char resolved_uri[1024];
const char *srvhost;
unsigned short srvport;
/* format of agiurl is "hagi://host.domain[:port][/script/name]" */
if (!(host = ast_strdupa(agiurl + 7))) { /* Remove hagi:// */
ast_log(LOG_WARNING, "An error occurred parsing the AGI URI: %s", agiurl);
return AGI_RESULT_FAILURE;
}
/* Strip off any script name */
if ((script = strchr(host, '/'))) {
*script++ = '\0';
} else {
script = "";
}
if (strchr(host, ':')) {
ast_log(LOG_WARNING, "Specifying a port number disables SRV lookups: %s\n", agiurl);
return launch_netscript(agiurl + 1, argv, fds); /* +1 to strip off leading h from hagi:// */
}
snprintf(service, sizeof(service), "%s%s", SRV_PREFIX, host);
while (!(srv_ret = ast_srv_lookup(&context, service, &srvhost, &srvport))) {
snprintf(resolved_uri, sizeof(resolved_uri), "agi://%s:%d/%s", srvhost, srvport, script);
result = launch_netscript(resolved_uri, argv, fds);
if (result == AGI_RESULT_FAILURE || result == AGI_RESULT_NOTFOUND) {
ast_log(LOG_WARNING, "AGI request failed for host '%s' (%s:%d)\n", host, srvhost, srvport);
} else {
break;
}
}
if (srv_ret < 0) {
ast_log(LOG_WARNING, "SRV lookup failed for %s\n", agiurl);
} else {
ast_srv_cleanup(&context);
}
return result;
}
static enum agi_result launch_script(struct ast_channel *chan, char *script, char *argv[], int *fds, int *efd, int *opid) static enum agi_result launch_script(struct ast_channel *chan, char *script, char *argv[], int *fds, int *efd, int *opid)
{ {
char tmp[256]; char tmp[256];
int pid, toast[2], fromast[2], audio[2], res; int pid, toast[2], fromast[2], audio[2], res;
struct stat st; struct stat st;
if (!strncasecmp(script, "agi://", 6)) if (!strncasecmp(script, "agi://", 6)) {
return launch_netscript(script, argv, fds, efd, opid); return (efd == NULL) ? launch_netscript(script, argv, fds) : AGI_RESULT_FAILURE;
if (!strncasecmp(script, "agi:async", sizeof("agi:async")-1)) }
if (!strncasecmp(script, "hagi://", 7)) {
return (efd == NULL) ? launch_ha_netscript(script, argv, fds) : AGI_RESULT_FAILURE;
}
if (!strncasecmp(script, "agi:async", sizeof("agi:async") - 1)) {
return launch_asyncagi(chan, argv, efd); return launch_asyncagi(chan, argv, efd);
}
if (script[0] != '/') { if (script[0] != '/') {
snprintf(tmp, sizeof(tmp), "%s/%s", ast_config_AST_AGI_DIR, script); snprintf(tmp, sizeof(tmp), "%s/%s", ast_config_AST_AGI_DIR, script);
@@ -3590,7 +3660,7 @@ static int agi_exec_full(struct ast_channel *chan, const char *data, int enhance
{ {
enum agi_result res; enum agi_result res;
char *buf; char *buf;
int fds[2], efd = -1, pid; int fds[2], efd = -1, pid = -1;
AST_DECLARE_APP_ARGS(args, AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(arg)[MAX_ARGS]; AST_APP_ARG(arg)[MAX_ARGS];
); );