# HG changeset patch # User Thomas Klausner # Date 1554817725 -7200 # Node ID f1b65381c1e79708cb3414aa2228498a0ce4f2f0 # Parent d66f60e29b22fed21538f90cea8d74e0dd808d40 rt_ereg: Support config reload. Support grouped AVPs. Support multiple separate AVPs. Written for Effortel Technologies SA, published with their consent. diff -r d66f60e29b22 -r f1b65381c1e7 doc/rt_ereg.conf.sample --- a/doc/rt_ereg.conf.sample Tue Apr 09 15:47:39 2019 +0200 +++ b/doc/rt_ereg.conf.sample Tue Apr 09 15:48:45 2019 +0200 @@ -3,9 +3,15 @@ # # The rt_ereg extension allows creation of routing rules based on AVP value matching regular expressions. +# This extension supports configuration reload at runtime. Send +# signal SIGUSR1 to the process to cause the process to reload its +# config. + # First, one must indicate which AVP should be used for matching. # At the moment, only AVP with OCTETSTRING types are valid. # AVP = "User-Name"; +# It is possible to specify AVPs below GROUPED AVPs with the by separating AVPs with a colon (':'): +# AVP = "Grouped-AVP1" : "Grouped-AVP2" : "Octetstring-AVP"; # This parameter is mandatory. There is no default value. # Then a list of rules follow. A rule has this format: @@ -19,3 +25,7 @@ # means that if the AVP value is only numeric, the ServerA will have its score decreased by 3 points. # (reminder: the server with the peer with the highest score gets the message) # Note that all rules are tested for each message that contain the AVP, not only the first match. + +# There can be multiple blocks of AVPs and rules; just start the next one with another AVP line: +# AVP = "Other-AVP"; +# and continue with rules as above. diff -r d66f60e29b22 -r f1b65381c1e7 extensions/rt_ereg/rtereg.c --- a/extensions/rt_ereg/rtereg.c Tue Apr 09 15:47:39 2019 +0200 +++ b/extensions/rt_ereg/rtereg.c Tue Apr 09 15:48:45 2019 +0200 @@ -33,16 +33,26 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * *********************************************************************************************************/ -/* +/* * This extension allows to perform some pattern-matching on an AVP * and send the message to a server accordingly. * See rt_ereg.conf.sample file for the format of the configuration file. */ +#include +#include + #include "rtereg.h" +static pthread_rwlock_t rte_lock; + +#define MODULE_NAME "rt_ereg" + +static char *rt_ereg_config_file; + /* The configuration structure */ -struct rtereg_conf rtereg_conf; +struct rtereg_conf *rtereg_conf; +int rtereg_conf_size; #ifndef HAVE_REG_STARTEND static char * buf = NULL; @@ -50,18 +60,41 @@ static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; #endif /* HAVE_REG_STARTEND */ -static int proceed(char * value, size_t len, struct fd_list * candidates) +static int rtereg_init(void); +static int rtereg_init_config(void); +static void rtereg_fini(void); + +void rtereg_conf_free(struct rtereg_conf *config_struct, int config_size) +{ + int i, j; + + /* Destroy the data */ + for (j=0; jpattern); - + #ifdef HAVE_REG_STARTEND { regmatch_t pmatch[1]; @@ -76,10 +109,10 @@ err = regexec(&r->preg, value, 0, NULL, 0); } #endif /* HAVE_REG_STARTEND */ - + if (err == REG_NOMATCH) continue; - + if (err != 0) { char * errstr; size_t bl; @@ -99,10 +132,10 @@ /* Free the buffer, return the error */ free(errstr); - + return (err == REG_ESPACE) ? ENOMEM : EINVAL; } - + /* From this point, the expression matched the AVP value */ TRACE_DEBUG(FULL, "[rt_ereg] Match: '%s' to value '%.*s' => '%s' += %d", r->pattern, @@ -110,7 +143,7 @@ value, r->server, r->score); - + for (c = candidates->next; c != candidates; c = c->next) { struct rtd_candidate * cand = (struct rtd_candidate *)c; @@ -120,106 +153,240 @@ } } }; - + + return 0; +} + +static int find_avp(msg_or_avp *where, int conf_index, int level, struct fd_list * candidates) +{ + struct dict_object *what; + struct dict_avp_data dictdata; + struct avp *nextavp = NULL; + struct avp_hdr *avp_hdr = NULL; + + /* iterate over all AVPs and try to find a match */ +// for (i = 0; i rtereg_conf[conf_index].level) { + TRACE_DEBUG(INFO, "internal error, dug too deep"); + return 1; + } + what = rtereg_conf[conf_index].avps[level]; + + CHECK_FCT(fd_dict_getval(what, &dictdata)); + CHECK_FCT(fd_msg_browse(where, MSG_BRW_FIRST_CHILD, (void *)&nextavp, NULL)); + while (nextavp) { + CHECK_FCT(fd_msg_avp_hdr(nextavp, &avp_hdr)); + if ((avp_hdr->avp_code == dictdata.avp_code) && (avp_hdr->avp_vendor == dictdata.avp_vendor)) { + if (level != rtereg_conf[conf_index].level - 1) { + TRACE_DEBUG(INFO, "[rt_ereg] found grouped AVP %d (vendor %d), digging deeper", avp_hdr->avp_code, avp_hdr->avp_vendor); + CHECK_FCT(find_avp(nextavp, conf_index, level+1, candidates)); + } else { + TRACE_DEBUG(INFO, "[rt_ereg] found AVP %d (vendor %d)", avp_hdr->avp_code, avp_hdr->avp_vendor); + if (avp_hdr->avp_value != NULL) { +#ifndef HAVE_REG_STARTEND + int ret; + + /* Lock the buffer */ + CHECK_POSIX( pthread_mutex_lock(&mtx) ); + + /* Augment the buffer if needed */ + if (avp_hdr->avp_value->os.len >= bufsz) { + CHECK_MALLOC_DO( buf = realloc(buf, avp_hdr->avp_value->os.len + 1), + { pthread_mutex_unlock(&mtx); return ENOMEM; } ); + } + + /* Copy the AVP value */ + memcpy(buf, avp_hdr->avp_value->os.data, avp_hdr->avp_value->os.len); + buf[avp_hdr->avp_value->os.len] = '\0'; + + /* Now apply the rules */ + ret = proceed(buf, avp_hdr->avp_value->os.len, candidates, conf_index); + + CHECK_POSIX(pthread_mutex_unlock(&mtx)); + + CHECK_FCT(ret); +#else /* HAVE_REG_STARTEND */ + CHECK_FCT( proceed((char *) avp_hdr->avp_value->os.data, avp_hdr->avp_value->os.len, candidates, conf_index) ); +#endif /* HAVE_REG_STARTEND */ + } + } + } + CHECK_FCT(fd_msg_browse(nextavp, MSG_BRW_NEXT, (void *)&nextavp, NULL)); + } + return 0; } /* The callback called on new messages */ static int rtereg_out(void * cbdata, struct msg ** pmsg, struct fd_list * candidates) { - struct msg * msg = *pmsg; - struct avp * avp = NULL; - - TRACE_ENTRY("%p %p %p", cbdata, msg, candidates); - - CHECK_PARAMS(msg && candidates); - - /* Check if it is worth processing the message */ - if (FD_IS_LIST_EMPTY(candidates)) { + msg_or_avp *where; + int j, ret; + + TRACE_ENTRY("%p %p %p", cbdata, *pmsg, candidates); + + CHECK_PARAMS(pmsg && *pmsg && candidates); + + if (pthread_rwlock_rdlock(&rte_lock) != 0) { + fd_log_notice("%s: read-lock failed, skipping handler", MODULE_NAME); return 0; } - - /* Now search the AVP in the message */ - CHECK_FCT( fd_msg_search_avp ( msg, rtereg_conf.avp, &avp ) ); - if (avp != NULL) { - struct avp_hdr * ahdr = NULL; - CHECK_FCT( fd_msg_avp_hdr ( avp, &ahdr ) ); - if (ahdr->avp_value != NULL) { -#ifndef HAVE_REG_STARTEND - int ret; - - /* Lock the buffer */ - CHECK_POSIX( pthread_mutex_lock(&mtx) ); - - /* Augment the buffer if needed */ - if (ahdr->avp_value->os.len >= bufsz) { - CHECK_MALLOC_DO( buf = realloc(buf, ahdr->avp_value->os.len + 1), - { pthread_mutex_unlock(&mtx); return ENOMEM; } ); + ret = 0; + /* Check if it is worth processing the message */ + if (!FD_IS_LIST_EMPTY(candidates)) { + /* Now search the AVPs in the message */ + + for (j=0; javp_value->os.data, ahdr->avp_value->os.len); - buf[ahdr->avp_value->os.len] = '\0'; - - /* Now apply the rules */ - ret = proceed(buf, ahdr->avp_value->os.len, candidates); - - CHECK_POSIX(pthread_mutex_unlock(&mtx)); - - CHECK_FCT(ret); -#else /* HAVE_REG_STARTEND */ - CHECK_FCT( proceed((char *) ahdr->avp_value->os.data, ahdr->avp_value->os.len, candidates) ); -#endif /* HAVE_REG_STARTEND */ } } - - return 0; + if (pthread_rwlock_unlock(&rte_lock) != 0) { + fd_log_notice("%s: read-unlock failed after rtereg_out, exiting", MODULE_NAME); + exit(1); + } + + return ret; } /* handler */ static struct fd_rt_out_hdl * rtereg_hdl = NULL; +static volatile int in_signal_handler = 0; + +/* signal handler */ +static void sig_hdlr(void) +{ + struct rtereg_conf *old_config; + int old_config_size; + + if (in_signal_handler) { + fd_log_error("%s: already handling a signal, ignoring new one", MODULE_NAME); + return; + } + in_signal_handler = 1; + + if (pthread_rwlock_wrlock(&rte_lock) != 0) { + fd_log_error("%s: locking failed, aborting config reload", MODULE_NAME); + return; + } + + /* save old config in case reload goes wrong */ + old_config = rtereg_conf; + old_config_size = rtereg_conf_size; + rtereg_conf = NULL; + rtereg_conf_size = 0; + + if (rtereg_init_config() != 0) { + fd_log_notice("%s: error reloading configuration, restoring previous configuration", MODULE_NAME); + rtereg_conf = old_config; + rtereg_conf_size = old_config_size; + } else { + rtereg_conf_free(old_config, old_config_size); + } + + if (pthread_rwlock_unlock(&rte_lock) != 0) { + fd_log_error("%s: unlocking failed after config reload, exiting", MODULE_NAME); + exit(1); + } + + fd_log_notice("%s: reloaded configuration, %d AVP group%s defined", MODULE_NAME, rtereg_conf_size, rtereg_conf_size != 1 ? "s" : ""); + + in_signal_handler = 0; +} + /* entry point */ static int rtereg_entry(char * conffile) { TRACE_ENTRY("%p", conffile); - + + rt_ereg_config_file = conffile; + + if (rtereg_init() != 0) { + return 1; + } + + /* Register reload callback */ + CHECK_FCT(fd_event_trig_regcb(SIGUSR1, MODULE_NAME, sig_hdlr)); + + fd_log_notice("%s: configured, %d AVP group%s defined", MODULE_NAME, rtereg_conf_size, rtereg_conf_size != 1 ? "s" : ""); + + return 0; +} + +static int rtereg_init_config(void) +{ /* Initialize the configuration */ - memset(&rtereg_conf, 0, sizeof(rtereg_conf)); - + if ((rtereg_conf=malloc(sizeof(*rtereg_conf))) == NULL) { + TRACE_DEBUG(INFO, "malloc failured"); + return 1; + } + rtereg_conf_size = 1; + memset(rtereg_conf, 0, sizeof(*rtereg_conf)); + /* Parse the configuration file */ - CHECK_FCT( rtereg_conf_handle(conffile) ); - + CHECK_FCT( rtereg_conf_handle(rt_ereg_config_file) ); + + return 0; +} + + +/* Load */ +static int rtereg_init(void) +{ + int ret; + + pthread_rwlock_init(&rte_lock, NULL); + + if (pthread_rwlock_wrlock(&rte_lock) != 0) { + fd_log_notice("%s: write-lock failed, aborting", MODULE_NAME); + return EDEADLK; + } + + if ((ret=rtereg_init_config()) != 0) { + pthread_rwlock_unlock(&rte_lock); + return ret; + } + + if (pthread_rwlock_unlock(&rte_lock) != 0) { + fd_log_notice("%s: write-unlock failed, aborting", MODULE_NAME); + return EDEADLK; + } + /* Register the callback */ CHECK_FCT( fd_rt_out_register( rtereg_out, NULL, 1, &rtereg_hdl ) ); - + /* We're done */ return 0; } /* Unload */ -void fd_ext_fini(void) +static void rtereg_fini(void) { - int i; TRACE_ENTRY(); - + /* Unregister the cb */ CHECK_FCT_DO( fd_rt_out_unregister ( rtereg_hdl, NULL ), /* continue */ ); - + /* Destroy the data */ - if (rtereg_conf.rules) - for (i = 0; i < rtereg_conf.rules_nb; i++) { - free(rtereg_conf.rules[i].pattern); - free(rtereg_conf.rules[i].server); - regfree(&rtereg_conf.rules[i].preg); - } - free(rtereg_conf.rules); + rtereg_conf_free(rtereg_conf, rtereg_conf_size); + rtereg_conf = NULL; + rtereg_conf_size = 0; #ifndef HAVE_REG_STARTEND free(buf); + buf = NULL; #endif /* HAVE_REG_STARTEND */ - + /* Done */ return ; } -EXTENSION_ENTRY("rt_ereg", rtereg_entry); +void fd_ext_fini(void) +{ + rtereg_fini(); +} + +EXTENSION_ENTRY(MODULE_NAME, rtereg_entry); diff -r d66f60e29b22 -r f1b65381c1e7 extensions/rt_ereg/rtereg.h --- a/extensions/rt_ereg/rtereg.h Tue Apr 09 15:47:39 2019 +0200 +++ b/extensions/rt_ereg/rtereg.h Tue Apr 09 15:48:45 2019 +0200 @@ -57,8 +57,11 @@ extern struct rtereg_conf { int rules_nb; /* Number of rules in the configuration */ struct rtereg_rule *rules; /* The array of rules */ - - struct dict_object * avp; /* cache the dictionary object that we are searching */ + + int level; /* how many levels of AVPs we have to dig down into */ + int finished; /* AVP fully configured, for configuration file reading */ + struct dict_object **avps; /* cache the dictionary objects that we are searching */ -} rtereg_conf; +} *rtereg_conf; +extern int rtereg_conf_size; diff -r d66f60e29b22 -r f1b65381c1e7 extensions/rt_ereg/rtereg_conf.y --- a/extensions/rt_ereg/rtereg_conf.y Tue Apr 09 15:47:39 2019 +0200 +++ b/extensions/rt_ereg/rtereg_conf.y Tue Apr 09 15:48:45 2019 +0200 @@ -37,14 +37,14 @@ */ /* For development only : */ -%debug +%debug %error-verbose /* The parser receives the configuration file filename as parameter */ %parse-param {char * conffile} /* Keep track of location */ -%locations +%locations %pure-parser %{ @@ -53,36 +53,91 @@ /* Forward declaration */ int yyparse(char * conffile); +void rtereg_confrestart(FILE *input_file); /* Parse the configuration file */ int rtereg_conf_handle(char * conffile) { extern FILE * rtereg_confin; int ret; - + TRACE_ENTRY("%p", conffile); - + TRACE_DEBUG (FULL, "Parsing configuration file: %s...", conffile); - + rtereg_confin = fopen(conffile, "r"); if (rtereg_confin == NULL) { ret = errno; fd_log_debug("Unable to open extension configuration file %s for reading: %s", conffile, strerror(ret)); - TRACE_DEBUG (INFO, "Error occurred, message logged -- configuration file."); + TRACE_DEBUG (INFO, "rt_ereg: error occurred, message logged -- configuration file."); return ret; } + rtereg_confrestart(rtereg_confin); ret = yyparse(conffile); fclose(rtereg_confin); + if (rtereg_conf[rtereg_conf_size-1].finished == 0) { + TRACE_DEBUG(INFO, "rt_ereg: configuration invalid, AVP ended without OCTETSTRING AVP"); + return EINVAL; + } + if (ret != 0) { - TRACE_DEBUG (INFO, "Unable to parse the configuration file."); + TRACE_DEBUG(INFO, "rt_ereg: unable to parse the configuration file."); return EINVAL; } else { - TRACE_DEBUG(FULL, "[rt-ereg] Added %d rules successfully.", rtereg_conf.rules_nb); + int i, sum = 0; + for (i=0; icnf_dict, DICT_AVP, AVP_BY_NAME, name, &rtereg_conf[rtereg_conf_size-1].avps[level-1], ENOENT ), + { + TRACE_DEBUG(INFO, "rt_ereg: Unable to find '%s' AVP in the loaded dictionaries.", name); + return -1; + } ); + + /* Now check the type */ + { + struct dict_avp_data data; + CHECK_FCT( fd_dict_getval( rtereg_conf[rtereg_conf_size-1].avps[level-1], &data) ); + if (data.avp_basetype == AVP_TYPE_OCTETSTRING) { + rtereg_conf[rtereg_conf_size-1].finished = 1; + } else if (data.avp_basetype != AVP_TYPE_GROUPED) { + TRACE_DEBUG(INFO, "rt_ereg: '%s' AVP is not an OCTETSTRING nor GROUPED AVP (%d).", name, data.avp_basetype); + return -1; + } + } + rtereg_conf[rtereg_conf_size-1].level = level; return 0; } @@ -92,8 +147,8 @@ /* Function to report the errors */ void yyerror (YYLTYPE *ploc, char * conffile, char const *s) { - TRACE_DEBUG(INFO, "Error in configuration parsing"); - + TRACE_DEBUG(INFO, "rt_ereg: error in configuration parsing"); + if (ploc->first_line != ploc->last_line) fd_log_debug("%s:%d.%d-%d.%d : %s", conffile, ploc->first_line, ploc->first_column, ploc->last_line, ploc->last_column, s); else if (ploc->first_column != ploc->last_column) @@ -125,61 +180,41 @@ %% /* The grammar definition */ -conffile: rules avp rules +conffile: avp rules + | conffile avp rules ; - + /* a server entry */ -avp: AVP '=' QSTRING ';' - { - if (rtereg_conf.avp != NULL) { - yyerror(&yylloc, conffile, "Only one AVP can be specified"); - YYERROR; - } - - CHECK_FCT_DO( fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, $3, &rtereg_conf.avp, ENOENT ), - { - TRACE_DEBUG(INFO, "Unable to find '%s' AVP in the loaded dictionaries.", $3); - yyerror (&yylloc, conffile, "Invalid AVP value."); - YYERROR; - } ); - - /* Now check the type */ - { - struct dict_avp_data data; - CHECK_FCT( fd_dict_getval( rtereg_conf.avp, &data) ); - CHECK_PARAMS_DO (data.avp_basetype == AVP_TYPE_OCTETSTRING, - { - TRACE_DEBUG(INFO, "'%s' AVP in not an OCTETSTRING AVP (%d).", $3, data.avp_basetype); - yyerror (&yylloc, conffile, "AVP in not an OCTETSTRING type."); - YYERROR; - } ); - } - } +avp: AVP '=' avp_part ';' ; - + +avp_part: avp_part ':' QSTRING { if (avp_add($3) < 0) { YYERROR; } } + | QSTRING { if (avp_add($1) < 0) { YYERROR; } } + ; + rules: /* empty OK */ | rules rule ; - + rule: QSTRING ':' QSTRING '+' '=' INTEGER ';' { struct rtereg_rule * new; int err; - + /* Add new rule in the array */ - rtereg_conf.rules_nb += 1; - CHECK_MALLOC_DO(rtereg_conf.rules = realloc(rtereg_conf.rules, rtereg_conf.rules_nb * sizeof(struct rtereg_rule)), + rtereg_conf[rtereg_conf_size-1].rules_nb += 1; + CHECK_MALLOC_DO(rtereg_conf[rtereg_conf_size-1].rules = realloc(rtereg_conf[rtereg_conf_size-1].rules, rtereg_conf[rtereg_conf_size-1].rules_nb * sizeof(struct rtereg_rule)), { yyerror (&yylloc, conffile, "Not enough memory to store the configuration..."); YYERROR; } ); - - new = &rtereg_conf.rules[rtereg_conf.rules_nb - 1]; - + + new = &rtereg_conf[rtereg_conf_size-1].rules[rtereg_conf[rtereg_conf_size-1].rules_nb - 1]; + new->pattern = $1; new->server = $3; new->score = $6; - + /* Attempt to compile the regex */ CHECK_FCT_DO( err=regcomp(&new->preg, new->pattern, REG_EXTENDED | REG_NOSUB), { @@ -187,7 +222,7 @@ size_t bl; /* Error while compiling the regex */ - TRACE_DEBUG(INFO, "Error while compiling the regular expression '%s':", new->pattern); + TRACE_DEBUG(INFO, "rt_ereg: error while compiling the regular expression '%s':", new->pattern); /* Get the error message size */ bl = regerror(err, &new->preg, NULL, 0); @@ -198,10 +233,10 @@ /* Get the error message content */ regerror(err, &new->preg, buf, bl); TRACE_DEBUG(INFO, "\t%s", buf); - + /* Free the buffer, return the error */ free(buf); - + yyerror (&yylloc, conffile, "Invalid regular expression."); YYERROR; } );