Index: CHANGES =================================================================== --- CHANGES (revision 234369) +++ CHANGES (working copy) @@ -1298,6 +1298,11 @@ * 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 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 -------------- Index: include/asterisk/srv.h =================================================================== --- include/asterisk/srv.h (revision 234369) +++ include/asterisk/srv.h (working copy) @@ -23,6 +23,8 @@ #ifndef _ASTERISK_SRV_H #define _ASTERISK_SRV_H +struct srv_context; + /*! \file srv.h \brief Support for DNS SRV records, used in to locate SIP services. @@ -31,6 +33,22 @@ no provisions for retrying or failover between records. */ +/*!\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 Only do SRV record lookup if you get a domain without a port. If you get a port #, it's a DNS host name. */ Index: main/srv.c =================================================================== --- main/srv.c (revision 234369) +++ main/srv.c (working copy) @@ -64,6 +64,7 @@ struct srv_context { unsigned int have_weights:1; + struct srv_entry *prev; AST_LIST_HEAD_NOLOCK(srv_entries, srv_entry) entries; }; @@ -197,6 +198,55 @@ 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) { struct srv_context context = { .entries = AST_LIST_HEAD_NOLOCK_INIT_VALUE }; Index: res/res_agi.c =================================================================== --- res/res_agi.c (revision 234369) +++ res/res_agi.c (working copy) @@ -60,6 +60,7 @@ #include "asterisk/features.h" #include "asterisk/term.h" #include "asterisk/xmldoc.h" +#include "asterisk/srv.h" #define AST_API_MODULE #include "asterisk/agi.h" @@ -897,6 +898,7 @@ #define MAX_CMD_LEN 80 #define AGI_NANDFS_RETRY 3 #define AGI_BUF_LEN 2048 +#define SRV_PREFIX "_agi._tcp." static char *app = "AGI"; @@ -1338,32 +1340,28 @@ /* launch_netscript: The fastagi handler. 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; struct pollfd pfds[1]; - char *host, *c, *script = ""; + char *host, *c, *script; struct sockaddr_in addr_in; struct hostent *hp; 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:// */ /* Strip off any script name */ - if ((c = strchr(host, '/'))) { - *c = '\0'; - c++; - script = c; + if ((script = strchr(host, '/'))) { + *script++ = '\0'; + } else { + script = ""; } + if ((c = strchr(host, ':'))) { - *c = '\0'; - c++; + *c++ = '\0'; 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))) { ast_log(LOG_WARNING, "Unable to locate host '%s'\n", host); return -1; @@ -1422,20 +1420,92 @@ ast_debug(4, "Wow, connected!\n"); fds[0] = s; fds[1] = s; - *opid = -1; 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) { char tmp[256]; int pid, toast[2], fromast[2], audio[2], res; struct stat st; - if (!strncasecmp(script, "agi://", 6)) - return launch_netscript(script, argv, fds, efd, opid); - if (!strncasecmp(script, "agi:async", sizeof("agi:async")-1)) + if (!strncasecmp(script, "agi://", 6)) { + return (efd == NULL) ? launch_netscript(script, argv, fds) : AGI_RESULT_FAILURE; + } + 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); + } if (script[0] != '/') { snprintf(tmp, sizeof(tmp), "%s/%s", ast_config_AST_AGI_DIR, script); @@ -3580,7 +3650,7 @@ { enum agi_result res; char *buf; - int fds[2], efd = -1, pid; + int fds[2], efd = -1, pid = -1; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(arg)[MAX_ARGS]; );