/* * app_distributor.c (distribute traffic) * * Copyright (c) 2004-2007 Anthony Minessale II * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include static char *tdesc = "Load balance a sequence of strings"; static char *app = "Distributor"; static char *synopsis = "Load balance a sequence of strings."; static char *desc = " Distributor()\n" "Sets the channel variable ${DISTRIBUTOR} to match the next logical string\n" "in the sequence as defined in distributor.conf\n" ""; STANDARD_LOCAL_USER; LOCAL_USER_DECL; AST_MUTEX_DEFINE_STATIC(mod_lock); struct dist_node { char *name; int weight; int cur_weight; struct dist_node *next; }; struct dist_list { char *name; int target_weight; int last; int node_count; struct dist_node *lastnode; struct dist_node *nodes; struct dist_list *next; }; static struct dist_list *global_list; static void destroy_node(struct dist_node *node) { struct dist_node *old; while(node) { old = node; node = node->next; if (old->name) { free(old->name); } free(old); } } static void destroy_list(struct dist_list *list) { struct dist_list *old; while(list) { old = list; list = list->next; destroy_node(old->nodes); if (old->name) { free(old->name); } free(old); } } static int load_config(int reloading) { struct dist_list *main_list=NULL, *new_list, *old_list=NULL, *lp=NULL; struct ast_config *cfg = NULL; char *entry; int res = -1; if (!(cfg = ast_config_load("distributor.conf"))) { return res; } ast_mutex_lock(&mod_lock); for (entry = ast_category_browse(cfg, NULL); entry != NULL; entry = ast_category_browse(cfg, entry)) { struct ast_variable *vp, *var = ast_variable_browse(cfg, entry); struct dist_node *node, *np=NULL; if (!(new_list=malloc(sizeof(*new_list)))) { ast_log(LOG_ERROR, "Mem Err!\n"); continue; } memset(new_list, 0, sizeof(*new_list)); new_list->name = strdup(entry); new_list->last = -1; if (lp) { lp->next = new_list; } else { main_list = new_list; } lp = new_list; for(vp = var ; vp ; vp = vp->next) { if (!strcasecmp(vp->name, "target_weight")) { new_list->target_weight = atoi(vp->value); } else if (!strcasecmp(vp->name, "member")){ char *name; char *wp; int weight; name = ast_strdupa(vp->value); if ((wp = strchr(name, ','))) { *wp++ = '\0'; } else { ast_log(LOG_WARNING, "invalid input %s missing weight\n", name); continue; } weight = lp->target_weight - atoi(wp); if (weight < 0 || weight > lp->target_weight) { ast_log(LOG_WARNING, "Weight %d value incorrect, must be between 1 and %d\n", weight, lp->target_weight); continue; } if (!(node = malloc(sizeof(*node)))) { ast_log(LOG_ERROR, "Mem Err!\n"); continue; } memset(node, 0, sizeof(*node)); node->name = strdup(name); node->weight = node->cur_weight = weight; if (np) { np->next = node; } else { lp->nodes = node; } np = node; lp->node_count++; } else { ast_log(LOG_WARNING, "Invalid Keyword %s\n", vp->name); } } } ast_config_destroy(cfg); if (main_list) { old_list = global_list; global_list = main_list; res = 0; } ast_mutex_unlock(&mod_lock); if (old_list) { destroy_list(old_list); } return res; } static int reset_list(struct dist_list *list) { struct dist_node *np; for(np = list->nodes; np; np = np->next) { np->cur_weight = np->weight; } list->last = -1; list->lastnode = NULL; return 0; } static struct dist_node *find_next(struct dist_list *list) { struct dist_node *np, *match = NULL; int x = 0, mx = 0; int matches = 0; for(;;) { x = 0; if (list->last >= list->node_count) { list->last = -1; } match = NULL; for(np = list->nodes; np; np = np->next) { if (np->cur_weight < list->target_weight) { ast_log(LOG_DEBUG, "%s %d/%d\n", np->name, np->cur_weight, list->target_weight); matches++; if (!match && x > list->last) { match = np; mx = x; } } x++; } if (match) { match->cur_weight++; list->lastnode = match; list->last = mx; ast_log(LOG_DEBUG, "Choose %s\n", match->name); return match; } ast_log(LOG_DEBUG, "Loop\n"); if (matches) { list->last = -1; } else { reset_list(list); } } return NULL; } static int dist_engine(char *name, char *buf, size_t len) { struct dist_node *np = NULL; struct dist_list *lp; int res = 0; for(lp = global_list; lp; lp = lp->next) { if (!strcasecmp(name, lp->name)) { np = find_next(lp); break; } } if (!np) { ast_log(LOG_WARNING, "Can't find anything!\n"); res = -1; } else { ast_copy_string(buf, np->name, len); res = 1; } return res; } static int distributor_exec(struct ast_channel *chan, void *data) { int res=0; struct localuser *u; char buf[512]; if (!data) { ast_log(LOG_WARNING, "distributor requires an argument\n"); return -1; } LOCAL_USER_ADD(u); ast_mutex_lock(&mod_lock); dist_engine(data, buf, sizeof(buf)); pbx_builtin_setvar_helper(chan, "DISTRIBUTOR", buf); ast_mutex_unlock(&mod_lock); LOCAL_USER_REMOVE(u); return res; } static char *function_distributor(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len) { char *ret = NULL; ast_mutex_lock(&mod_lock); if (dist_engine(data, buf, len) == 1) { ret = buf; } ast_mutex_unlock(&mod_lock); return ret; } static struct ast_custom_function distributor_function = { .name = "DISTRIBUTOR", .desc = "Returns the next logical string in the sequence \n" "as defined in distributor.conf", .syntax = "DISTRIBUTOR()", .synopsis = "Picks the next string in a sequence.", .read = function_distributor }; int reload(void) { return load_config(1); } int unload_module(void) { STANDARD_HANGUP_LOCALUSERS; ast_custom_function_unregister(&distributor_function); return ast_unregister_application(app); } int load_module(void) { load_config(0); ast_custom_function_register(&distributor_function); return ast_register_application(app, distributor_exec, synopsis, desc); } char *description(void) { return tdesc; } int usecount(void) { int res; STANDARD_USECOUNT(res); return res; } char *key() { return ASTERISK_GPL_KEY; }