From 3419f933380ad563d8df0bccb2c425a4cba211b4 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Tue, 13 Mar 2018 16:44:58 +0200 Subject: [PATCH 01/10] [WIP] cgrates: add compat_mode parameter and $cgr_opt() --- modules/cgrates/README | 198 ++++++++++++++++++-------- modules/cgrates/cgrates.c | 68 ++++++++- modules/cgrates/cgrates_acc.c | 20 +-- modules/cgrates/cgrates_auth.c | 2 +- modules/cgrates/cgrates_common.c | 19 +-- modules/cgrates/cgrates_common.h | 7 +- modules/cgrates/cgrates_engine.h | 1 + modules/cgrates/doc/cgrates_admin.xml | 120 ++++++++++++++-- 8 files changed, 338 insertions(+), 97 deletions(-) diff --git a/modules/cgrates/README b/modules/cgrates/README index ac6735a430..b64865f1b1 100644 --- a/modules/cgrates/README +++ b/modules/cgrates/README @@ -16,39 +16,42 @@ Razvan Crainea 1.3. Accounting 1.4. Other Commands 1.5. CGRateS Failover - 1.6. Dependencies + 1.6. + 1.7. Dependencies - 1.6.1. OpenSIPS Modules - 1.6.2. External Libraries or Applications + 1.7.1. OpenSIPS Modules + 1.7.2. External Libraries or Applications - 1.7. Exported Parameters + 1.8. Exported Parameters - 1.7.1. cgrates_engine (string) - 1.7.2. bind_ip (string) - 1.7.3. max_async_connections (integer) - 1.7.4. retry_timeout (integer) + 1.8.1. cgrates_engine (string) + 1.8.2. bind_ip (string) + 1.8.3. max_async_connections (integer) + 1.8.4. retry_timeout (integer) + 1.8.5. compat_mode (integer) - 1.8. Exported Functions + 1.9. Exported Functions - 1.8.1. cgrates_acc([flags[, account[, destination[, + 1.9.1. cgrates_acc([flags[, account[, destination[, session]]]]) - 1.8.2. cgrates_auth([account[, destination[, + 1.9.2. cgrates_auth([account[, destination[, session]]]) - 1.8.3. cgrates_cmd(command[, session]) + 1.9.3. cgrates_cmd(command[, session]) - 1.9. Exported pseudo-variables + 1.10. Exported pseudo-variables - 1.9.1. $cgr(name) / $(cgr(name)[session]) - 1.9.2. $cgrret + 1.10.1. $cgr(name) / $(cgr(name)[session]) + 1.10.2. $cgr_opt(name) / $(cgr_opt(name)[session]) + 1.10.3. $cgrret - 1.10. Exported Asynchronous Functions + 1.11. Exported Asynchronous Functions - 1.10.1. cgrates_auth([account[, destination[, + 1.11.1. cgrates_auth([account[, destination[, session]]]) - 1.10.2. cgrates_cmd(command[, session]) + 1.11.2. cgrates_cmd(command[, session]) List of Examples @@ -56,14 +59,17 @@ Razvan Crainea 1.2. Set bind_ip parameter 1.3. Set max_async_connections parameter 1.4. Set retry_timeout parameter - 1.5. cgrates_acc() usage - 1.6. cgrates_auth() usage - 1.7. cgrates_cmd() usage - 1.8. $cgr(name) simple usage - 1.9. $cgr(name) multiple sessions usage - 1.10. $cgrret usage - 1.11. async cgrates_auth usage - 1.12. async cgrates_cmd usage + 1.5. Set compat_mode parameter + 1.6. cgrates_acc() usage + 1.7. cgrates_auth() usage + 1.8. cgrates_cmd() usage + 1.9. $cgr(name) simple usage + 1.10. $cgr(name) multiple sessions usage + 1.11. $cgr_opt(name) usage + 1.12. $cgrret usage + 1.13. async cgrates_auth usage + 1.14. async cgrates_cmd compat_mode usage + 1.15. async cgrates_cmd new usage Chapter 1. Admin Guide @@ -160,7 +166,8 @@ Chapter 1. Admin Guide The following example simulates the cgrates_auth() CGRateS call: ... - $cgr(Tenant) = $fd; + $cgr_opt(Tenant) = $fd; # or $cgr(Tenant) = $fd; /* in c +ompat mode */ $cgr(Account) = $fU; $cgr(OriginID) = $ci; $cgr(SetupTime) = "" + $Ts; @@ -189,22 +196,33 @@ Chapter 1. Admin Guide enabled. Otherwise, the other engines are used, until none is available and the command fails. -1.6. Dependencies + The module supports two different versions of CGRateS: the + compat_mode one, which works with pre-rc8 releases, and a new + one which works with the post-rc8 releases. The difference + between the two versions consist in the way the requests and + responses to and from CGRateS are built. In the + non-compat_mode/new version, a new variable, $cgr_opt(), is + available, and can be used to tune the request options. This + variable should not be used in compat_mode mode to avoid + abiguities, but if it is used, it behaves exactly as $cgr(). By + default compat_mode is disabled. -1.6.1. OpenSIPS Modules +1.7. Dependencies + +1.7.1. OpenSIPS Modules The following modules must be loaded before this module: * dialog -- in case CGRateS accounting is used. -1.6.2. External Libraries or Applications +1.7.2. External Libraries or Applications The following libraries or applications must be installed before running OpenSIPS with this module loaded: * libjson -1.7. Exported Parameters +1.8. Exported Parameters -1.7.1. cgrates_engine (string) +1.8.1. cgrates_engine (string) This parameter is used to specify a CGRateS engine connection. The format is IP[:port]. The port is optional, and if missing, @@ -221,7 +239,7 @@ modparam("cgrates", "cgrates_engine", "127.0.0.1") modparam("cgrates", "cgrates_engine", "127.0.0.1:2013") ... -1.7.2. bind_ip (string) +1.8.2. bind_ip (string) IP used to bind the socket that communicates with the CGRateS engines. This is useful to set when the engine is runing in a @@ -235,7 +253,7 @@ modparam("cgrates", "cgrates_engine", "127.0.0.1:2013") modparam("cgrates", "bind_ip", "10.0.0.100") ... -1.7.3. max_async_connections (integer) +1.8.3. max_async_connections (integer) The maximum number of simultaneous asynchronous connections to a CGRateS engine. @@ -247,7 +265,7 @@ modparam("cgrates", "bind_ip", "10.0.0.100") modparam("cgrates", "max_async_connections", 20) ... -1.7.4. retry_timeout (integer) +1.8.4. retry_timeout (integer) The number of seconds after which a disabled connection/engine is retried. @@ -259,9 +277,21 @@ modparam("cgrates", "max_async_connections", 20) modparam("cgrates", "retry_timeout", 120) ... -1.8. Exported Functions +1.8.5. compat_mode (integer) + + Indicates whether OpenSIPS should use the old (compat_mode) + CGRateS version API (pre-rc8). + + Default value is "false (0)". -1.8.1. cgrates_acc([flags[, account[, destination[, session]]]]) + Example 1.5. Set compat_mode parameter +... +modparam("cgrates", "compat_mode", 1) +... + +1.9. Exported Functions + +1.9.1. cgrates_acc([flags[, account[, destination[, session]]]]) cgrates_acc() starts an accounting session on the CGRateS engine for the current dialog. It also ends the session when @@ -312,7 +342,7 @@ modparam("cgrates", "retry_timeout", 120) This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE and LOCAL_ROUTE. - Example 1.5. cgrates_acc() usage + Example 1.6. cgrates_acc() usage ... if (!has_totag()) { ... @@ -322,7 +352,7 @@ modparam("cgrates", "retry_timeout", 120) } ... -1.8.2. cgrates_auth([account[, destination[, session]]]) +1.9.2. cgrates_auth([account[, destination[, session]]]) cgrates_auth() does call authorization through using the CGRateS engine. @@ -353,7 +383,7 @@ modparam("cgrates", "retry_timeout", 120) This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE and LOCAL_ROUTE. - Example 1.6. cgrates_auth() usage + Example 1.7. cgrates_auth() usage ... if (!has_totag()) { ... @@ -365,7 +395,7 @@ modparam("cgrates", "retry_timeout", 120) } ... -1.8.3. cgrates_cmd(command[, session]) +1.9.3. cgrates_cmd(command[, session]) cgrates_cmd() can send arbitrary commands to the CGRateS engine. @@ -389,10 +419,10 @@ modparam("cgrates", "retry_timeout", 120) This function can be used from any route. - Example 1.7. cgrates_cmd() usage + Example 1.8. cgrates_cmd() usage ... # cgrates_auth("$fU", "$rU"); simulation - $cgr(Tenant) = $fd; + $cgr_opt(Tenant) = $fd; $cgr(Account) = $fU; $cgr(OriginID) = $ci; $cgr(SetupTime) = "" + $Ts; @@ -402,9 +432,9 @@ modparam("cgrates", "retry_timeout", 120) xlog("Call is allowed to run $cgrret seconds\n"); ... -1.9. Exported pseudo-variables +1.10. Exported pseudo-variables -1.9.1. $cgr(name) / $(cgr(name)[session]) +1.10.1. $cgr(name) / $(cgr(name)[session]) Pseudo-variable used to set different parameters for the CGRateS command. Each name-value pair will be encoded as a @@ -424,12 +454,12 @@ modparam("cgrates", "retry_timeout", 120) another. if the session tag does not exist, the default (no name) one is used. - Example 1.8. $cgr(name) simple usage + Example 1.9. $cgr(name) simple usage ... if (!has_totag()) { ... - $cgr(Tenant) = $fd; # set the From domain as a t -enant + $cgr_opt(Tenant) = $fd; # set the From domain as + a tenant $cgr(RequestType) = "*prepaid"; # do prepaid acc ounting if (!cgrates_auth("$fU", "$rU")) { @@ -439,13 +469,13 @@ ounting } ... - Example 1.9. $cgr(name) multiple sessions usage + Example 1.10. $cgr(name) multiple sessions usage ... if (!has_totag()) { ... # first session - authorize the user - $cgr(Tenant) = $fd; # set the From domain as a t -enant + $cgr_opt(Tenant) = $fd; # set the From domain as + a tenant $cgr(RequestType) = "*prepaid"; # do prepaid acc ounting if (!cgrates_auth("$fU", "$rU")) { @@ -454,7 +484,7 @@ ounting } # second session - authorize the carrier - $(cgr(Tenant)[carrier]) = $td; + $(cgr_opt(Tenant)[carrier]) = $td; $(cgr(RequestType)[carrier]) = "*postpaid"; if (!cgrates_auth("$tU", "$fU", "carrier")) { # use a different carrier @@ -468,19 +498,46 @@ ounting } ... -1.9.2. $cgrret +1.10.2. $cgr_opt(name) / $(cgr_opt(name)[session]) + + Used to tune the request parameter of a CGRateS request when + used in non-compat_mode. + + Note: for all request options integer values act as boolean + values: 0 disables the feature and 1(or different than 0 value) + enables it. String variables are passed just as they are set. + + Possible values at the time the documentation was written: + * Tenant - tune CGRateS Tenant. + * GetAttributes - requests the account attributes from the + CGRateS DB. + * GetMaxUsage - request the maximum time the call is allowed + to run. + * GetSuppliers - request an array with all the suppliers for + that can terminate that call. + + Example 1.11. $cgr_opt(name) usage + ... + $cgr_opt(Tenant) = "cgrates.org"; + $cgr_opt(GetMaxUsage) = 1; # also retrieve the max usage + if (!cgrates_auth("$fU", "$rU")) { + # call rejected + } + ... + +1.10.3. $cgrret Returns the reply message of a CGRateS command in script. - Example 1.10. $cgrret usage + Example 1.12. $cgrret usage ... cgrates_auth("$fU", "$rU"); xlog("Call is allowed to run $cgrret seconds\n"); ... -1.10. Exported Asynchronous Functions +1.11. Exported Asynchronous Functions -1.10.1. cgrates_auth([account[, destination[, session]]]) +1.11.1. cgrates_auth([account[, destination[, session]]]) Does the CGRateS authorization call in an asynchronous way. Script execution is suspended until the CGRateS engine sends @@ -509,7 +566,7 @@ ounting headers, or it is not an initial INVITE. * -5 - CGRateS returned an invalid message. - Example 1.11. async cgrates_auth usage + Example 1.13. async cgrates_auth usage route { ... async(cgrates_auth("$fU", "$rU"), auth_reply); @@ -525,7 +582,7 @@ route [auth_reply] ... } -1.10.2. cgrates_cmd(command[, session]) +1.11.2. cgrates_cmd(command[, session]) Can run an arbitrary CGRateS command in an asynchronous way. The execution is suspended until the CGRateS engine sends the @@ -548,7 +605,7 @@ route [auth_reply] * -3 - No suitable CGRateS server found. message type (not an initial INVITE). - Example 1.12. async cgrates_cmd usage + Example 1.14. async cgrates_cmd compat_mode usage route { ... $cgr(Tenant) = $fd; @@ -560,6 +617,29 @@ route { async(cgrates_cmd("SMGenericV1.GetMaxUsage"), auth_reply); } +route [auth_reply] +{ + if ($rc < 0) { + xlog("Call not authorized: code=$cgrret!\n"); + send_reply("403", "Forbidden"); + exit; + } + ... +} + + Example 1.15. async cgrates_cmd new usage +route { + ... + $cgr_opt(Tenant) = $fd; + $cgr(Account) = $fU; + $cgr(OriginID) = $ci; + $cgr(SetupTime) = "" + $Ts; + $cgr(RequestType) = "*prepaid"; + $cgr(Destination) = $rU; + async(cgrates_cmd("SessionSv1.AuthorizeEventWithDigest"), auth_r +eply); +} + route [auth_reply] { if ($rc < 0) { diff --git a/modules/cgrates/cgrates.c b/modules/cgrates/cgrates.c index c6cf21efc0..b52d837516 100644 --- a/modules/cgrates/cgrates.c +++ b/modules/cgrates/cgrates.c @@ -40,6 +40,7 @@ #include "cgrates_common.h" #include "cgrates_engine.h" +int cgre_compat_mode = 0; int cgre_retry_tout = CGR_DEFAULT_RETRY_TIMEOUT; int cgrc_max_conns = CGR_DEFAULT_MAX_CONNS; str cgre_bind_ip; @@ -56,10 +57,19 @@ int cgr_ctx_local_idx; int cgr_tm_ctx_idx = -1; static int pv_set_cgr(struct sip_msg *msg, pv_param_t *param, + int op, pv_value_t *val, int reqopt); +static int w_pv_set_cgr(struct sip_msg *msg, pv_param_t *param, + int op, pv_value_t *val); +static int w_pv_set_cgr_opt(struct sip_msg *msg, pv_param_t *param, int op, pv_value_t *val); static int pv_get_cgr(struct sip_msg *msg, pv_param_t *param, + pv_value_t *val, int reqopt); +static int w_pv_get_cgr(struct sip_msg *msg, pv_param_t *param, + pv_value_t *val); +static int w_pv_get_cgr_opt(struct sip_msg *msg, pv_param_t *param, pv_value_t *val); static int pv_parse_cgr(pv_spec_p sp, str *in); +static int w_pv_parse_cgr(pv_spec_p sp, str *in); static int pv_parse_idx_cgr(pv_spec_p sp, str *in); static int pv_get_cgr_reply(struct sip_msg *msg, pv_param_t *param, pv_value_t *val); @@ -91,9 +101,11 @@ static cmd_export_t cmds[] = { }; static pv_export_t pvars[] = { - { str_init("cgr"), 2003, pv_get_cgr, pv_set_cgr, + { str_init("cgr"), 2003, w_pv_get_cgr, w_pv_set_cgr, pv_parse_cgr, pv_parse_idx_cgr, 0, 0}, - { str_init("cgrret"), 2004, pv_get_cgr_reply, + { str_init("cgr_opt"), 2004, w_pv_get_cgr_opt, w_pv_set_cgr_opt, + w_pv_parse_cgr, pv_parse_idx_cgr, 0, 0}, + { str_init("cgrret"), 2005, pv_get_cgr_reply, 0, 0, 0, 0, 0}, { {0, 0}, 0, 0, 0, 0, 0, 0, 0 } }; @@ -115,6 +127,7 @@ static param_export_t params[] = { {"bind_ip", STR_PARAM, &cgre_bind_ip.s }, {"max_async_connections", INT_PARAM, &cgrc_max_conns }, {"retry_timeout", INT_PARAM, &cgre_retry_tout }, + {"compat_mode", INT_PARAM, &cgre_compat_mode }, {0, 0, 0} }; @@ -366,9 +379,10 @@ static inline str *pv_get_idx_value(struct sip_msg *msg, pv_param_t *param) } static int pv_set_cgr(struct sip_msg *msg, pv_param_t *param, - int op, pv_value_t *val) + int op, pv_value_t *val, int reqopt) { pv_value_t name_val; + struct list_head *kvs; struct cgr_session *s; struct cgr_ctx *ctx; struct cgr_kv *kv; @@ -409,8 +423,10 @@ static int pv_set_cgr(struct sip_msg *msg, pv_param_t *param, return -2; } + kvs = (reqopt? &s->req_kvs: &s->event_kvs); + /* check if there already is a kv with that name */ - kv = cgr_get_kv(s, name_val.rs); + kv = cgr_get_kv(kvs, name_val.rs); if (kv) { /* replace the old value */ cgr_free_kv_val(kv); @@ -425,7 +441,7 @@ static int pv_set_cgr(struct sip_msg *msg, pv_param_t *param, LM_ERR("cannot allocate new key-value\n"); return -1; } - list_add(&kv->list, &s->kvs); + list_add(&kv->list, kvs); } if (val->flags & PV_VAL_NULL) { kv->flags |= CGR_KVF_TYPE_NULL; @@ -450,13 +466,26 @@ static int pv_set_cgr(struct sip_msg *msg, pv_param_t *param, return -1; } +static int w_pv_set_cgr(struct sip_msg *msg, pv_param_t *param, + int op, pv_value_t *val) +{ + return pv_set_cgr(msg, param, op, val, 0); +} + +static int w_pv_set_cgr_opt(struct sip_msg *msg, pv_param_t *param, + int op, pv_value_t *val) +{ + return pv_set_cgr(msg, param, op, val, cgre_compat_mode? 0: 1); +} + static int pv_get_cgr(struct sip_msg *msg, pv_param_t *param, - pv_value_t *val) + pv_value_t *val, int reqopt) { pv_value_t name_val; struct cgr_ctx *ctx; struct cgr_kv *kv; struct cgr_session *s; + struct list_head *kvs; if (!param || !val) { LM_ERR("invalid parameter or value to set\n"); @@ -485,8 +514,10 @@ static int pv_get_cgr(struct sip_msg *msg, pv_param_t *param, name_val.rs = param->pvn.u.isname.name.s; } + kvs = (reqopt? &s->req_kvs: &s->event_kvs); + /* check if there already is a kv with that name */ - if (!(kv = cgr_get_kv(s, name_val.rs)) || \ + if (!(kv = cgr_get_kv(kvs, name_val.rs)) || \ kv->flags & CGR_KVF_TYPE_NULL) return pv_get_null(msg, param, val); @@ -503,6 +534,18 @@ static int pv_get_cgr(struct sip_msg *msg, pv_param_t *param, return 0; } +static int w_pv_get_cgr(struct sip_msg *msg, pv_param_t *param, + pv_value_t *val) +{ + return pv_get_cgr(msg, param, val, 0); +} + +static int w_pv_get_cgr_opt(struct sip_msg *msg, pv_param_t *param, + pv_value_t *val) +{ + return pv_get_cgr(msg, param, val, cgre_compat_mode? 0: 1); +} + static int pv_get_cgr_reply(struct sip_msg *msg, pv_param_t *param, pv_value_t *val) { @@ -566,6 +609,17 @@ static int pv_parse_cgr(pv_spec_p sp, str *in) return 0; } +static int w_pv_parse_cgr(pv_spec_p sp, str *in) +{ + if (cgre_compat_mode) { + LM_WARN("using $cgr_opt(%.*s) in compat mode is not possible!\n", + in->len, in->s); + LM_WARN("using $cgr_opt(%.*s) exactly as $cgr(NAME)!\n", + in->len, in->s); + } + return pv_parse_cgr(sp, in); +} + static int pv_parse_idx_cgr(pv_spec_p sp, str *in) { str *s; diff --git a/modules/cgrates/cgrates_acc.c b/modules/cgrates/cgrates_acc.c index b914bb7337..8a50e09e25 100644 --- a/modules/cgrates/cgrates_acc.c +++ b/modules/cgrates/cgrates_acc.c @@ -125,13 +125,13 @@ struct cgr_acc_ctx *cgr_tryget_acc_ctx(void) /* if there is a matching session. move everything from * the one in accounting to the one in local ctx */ if (s) { - list_for_each_safe(l, t, &sa->kvs) { + list_for_each_safe(l, t, &sa->event_kvs) { kv = list_entry(l, struct cgr_kv, list); - if (cgr_get_kv(s, kv->key)) + if (cgr_get_kv(&s->event_kvs, kv->key)) cgr_free_kv(kv); else { list_del(&kv->list); - list_add(&kv->list, &s->kvs); + list_add(&kv->list, &s->event_kvs); } } if (s->acc_info) { @@ -333,7 +333,7 @@ static json_object *cgr_get_start_acc_msg(struct sip_msg *msg, /* OriginID */ /* if origin was not added from script, add it now */ - if (ctx && !cgr_get_const_kv(s, "OriginID")) { + if (ctx && !cgr_get_const_kv(&s->event_kvs, "OriginID")) { if (msg->callid==NULL && ((parse_headers(msg, HDR_CALLID_F, 0)==-1) || (msg->callid==NULL)) ) { LM_ERR("Cannot get callid of the message!\n"); @@ -347,13 +347,13 @@ static json_object *cgr_get_start_acc_msg(struct sip_msg *msg, } } - if (ctx && !cgr_get_const_kv(s, "DialogID") && + if (ctx && !cgr_get_const_kv(&s->event_kvs, "DialogID") && cgr_msg_push_int(cmsg, "DialogID", dlg->h_id) < 0) { LM_ERR("cannot push DialogID!\n"); goto error; } - if (ctx && !cgr_get_const_kv(s, "DialogEntry") && + if (ctx && !cgr_get_const_kv(&s->event_kvs, "DialogEntry") && cgr_msg_push_int(cmsg, "DialogEntry", dlg->h_entry) < 0) { LM_ERR("cannot push DialogEntry!\n"); goto error; @@ -426,7 +426,7 @@ static json_object *cgr_get_stop_acc_msg(struct sip_msg *msg, /* OriginID */ /* if origin was not added from script, add it now */ - if (ctx && !cgr_get_const_kv(s, "OriginID")) { + if (ctx && !cgr_get_const_kv(&s->event_kvs, "OriginID")) { callid = cgr_get_sess_callid(msg, s, &dlg->callid); if (cgr_msg_push_str(cmsg, "OriginID", callid) < 0) { LM_ERR("cannot push OriginID!\n"); @@ -602,7 +602,7 @@ static void cgr_dlg_onshutdown(struct dlg_cell *dlg, int type, sizeof(unsigned) /* number of keys */; /* count the keys now */ - list_for_each(ls, &s->kvs) { + list_for_each(ls, &s->event_kvs) { sessions_kvs[sessions_no-1]++; kv = list_entry(ls, struct cgr_kv, list); buf.len += sizeof(unsigned) + kv->key.len + sizeof(unsigned char); @@ -676,7 +676,7 @@ static void cgr_dlg_onshutdown(struct dlg_cell *dlg, int type, memcpy(p, &sessions_kvs[i], sizeof(unsigned)); p += sizeof(unsigned); - list_for_each(ls, &s->kvs) { + list_for_each(ls, &s->event_kvs) { /* if does not have acc_info, it does not have start_time */ kv = list_entry(ls, struct cgr_kv, list); @@ -1087,7 +1087,7 @@ void cgr_loaded_callback(struct dlg_cell *dlg, int type, CGR_CTX_COPY(kv->value.s.s, kv->value.s.len, "key.value.str.s"); } /* all good - link the new value */ - list_add(&kv->list, &s->kvs); + list_add(&kv->list, &s->event_kvs); } } store: diff --git a/modules/cgrates/cgrates_auth.c b/modules/cgrates/cgrates_auth.c index df9413e74a..4655241c86 100644 --- a/modules/cgrates/cgrates_auth.c +++ b/modules/cgrates/cgrates_auth.c @@ -76,7 +76,7 @@ static json_object *cgr_get_auth_msg(struct sip_msg *msg, str *acc, str *dst, st /* OriginID */ /* if origin was not added from script, add it now */ - if (((s && !cgr_get_const_kv(s, "OriginID")) || !s) && + if (((s && !cgr_get_const_kv(&s->event_kvs, "OriginID")) || !s) && cgr_msg_push_str(cmsg, "OriginID", &msg->callid->body) < 0) { LM_ERR("cannot push OriginID!\n"); goto error; diff --git a/modules/cgrates/cgrates_common.c b/modules/cgrates/cgrates_common.c index ab931286ad..cd4cbe9f95 100644 --- a/modules/cgrates/cgrates_common.c +++ b/modules/cgrates/cgrates_common.c @@ -35,14 +35,12 @@ #include "cgrates_engine.h" /* key-value manipulation */ -struct cgr_kv *cgr_get_kv(struct cgr_session *s, str name) +struct cgr_kv *cgr_get_kv(struct list_head *list, str name) { struct list_head *l; struct cgr_kv *kv; - if (!s) - return NULL; - list_for_each(l, &s->kvs) { + list_for_each(l, list) { kv = list_entry(l, struct cgr_kv, list); if (kv->key.len == name.len && !memcmp(kv->key.s, name.s, name.len)) return kv; @@ -50,12 +48,12 @@ struct cgr_kv *cgr_get_kv(struct cgr_session *s, str name) return NULL; } -struct cgr_kv *cgr_get_const_kv(struct cgr_session *s, const char *name) +struct cgr_kv *cgr_get_const_kv(struct list_head *list, const char *name) { str sname; sname.s = (char *)name; sname.len = strlen(name); - return cgr_get_kv(s, sname); + return cgr_get_kv(list, sname); } struct cgr_kv *cgr_new_real_kv(char *key, int klen, int dup) @@ -205,7 +203,7 @@ struct cgr_msg *cgr_get_generic_msg(str *method, struct cgr_session *s) json_object_array_add(jarr, cmsg.params); if (s) { - list_for_each(l, &s->kvs) { + list_for_each(l, &s->event_kvs) { kv = list_entry(l, struct cgr_kv, list); if (kv->flags & CGR_KVF_TYPE_NULL) { jtmp = NULL; @@ -345,7 +343,8 @@ struct cgr_session *cgr_new_sess(str *tag) s->tag.len = 0; } s->acc_info = 0; - INIT_LIST_HEAD(&s->kvs); + INIT_LIST_HEAD(&s->event_kvs); + INIT_LIST_HEAD(&s->req_kvs); return s; } @@ -780,7 +779,9 @@ void cgr_free_sess(struct cgr_session *s) if (s->acc_info) shm_free(s->acc_info); - list_for_each_safe(l, t, &s->kvs) + list_for_each_safe(l, t, &s->event_kvs) + cgr_free_kv(list_entry(l, struct cgr_kv, list)); + list_for_each_safe(l, t, &s->req_kvs) cgr_free_kv(list_entry(l, struct cgr_kv, list)); list_del(&s->list); shm_free(s); diff --git a/modules/cgrates/cgrates_common.h b/modules/cgrates/cgrates_common.h index 8943046487..1091203e2e 100644 --- a/modules/cgrates/cgrates_common.h +++ b/modules/cgrates/cgrates_common.h @@ -52,8 +52,9 @@ struct cgr_acc_sess; struct cgr_session { str tag; - struct list_head kvs; struct list_head list; + struct list_head req_kvs; + struct list_head event_kvs; struct cgr_acc_sess *acc_info; }; @@ -109,8 +110,8 @@ struct cgr_kv *cgr_new_real_kv(char *key, int klen, int dup); void cgr_free_kv(struct cgr_kv *kv); void cgr_free_kv_val(struct cgr_kv *kv); void cgr_free_sess(struct cgr_session *sess); -struct cgr_kv *cgr_get_kv(struct cgr_session *sess, str name);; -struct cgr_kv *cgr_get_const_kv(struct cgr_session *sess, const char *name); +struct cgr_kv *cgr_get_kv(struct list_head *list, str name); +struct cgr_kv *cgr_get_const_kv(struct list_head *list, const char *name); struct cgr_session *cgr_get_sess(struct cgr_ctx *ctx, str *name); struct cgr_session *cgr_new_sess(str *tag); struct cgr_session *cgr_get_sess_new(struct cgr_ctx *ctx, str *name); diff --git a/modules/cgrates/cgrates_engine.h b/modules/cgrates/cgrates_engine.h index c23873be1a..36bca10495 100644 --- a/modules/cgrates/cgrates_engine.h +++ b/modules/cgrates/cgrates_engine.h @@ -51,6 +51,7 @@ extern struct list_head cgrates_engines; #define CGRC_IS_DEFAULT(_c) ((_c)->flags & CGRF_DEFAULT) #define CGRC_SET_DEFAULT(_c) (_c)->flags |= CGRF_DEFAULT +extern int cgre_compat_mode; extern int cgre_retry_tout; extern int cgrc_max_conns; extern str cgre_bind_ip; diff --git a/modules/cgrates/doc/cgrates_admin.xml b/modules/cgrates/doc/cgrates_admin.xml index e6f1888557..96396543ff 100644 --- a/modules/cgrates/doc/cgrates_admin.xml +++ b/modules/cgrates/doc/cgrates_admin.xml @@ -116,7 +116,7 @@ The following example simulates the cgrates_auth() CGRateS call: ... - $cgr(Tenant) = $fd; + $cgr_opt(Tenant) = $fd; # or $cgr(Tenant) = $fd; /* in compat mode */ $cgr(Account) = $fU; $cgr(OriginID) = $ci; $cgr(SetupTime) = "" + $Ts; @@ -151,6 +151,21 @@ +
+ + The module supports two different versions of CGRateS: the + compat_mode one, which works with pre-rc8 releases, and a + new one which works with the post-rc8 releases. The difference between the two + versions consist in the way the requests and responses to and from CGRateS + are built. In the non-compat_mode/new version, a new + variable, $cgr_opt(), is available, and can be used to + tune the request options. This variable should not be used in + compat_mode mode to avoid abiguities, but if it is used, + it behaves exactly as $cgr(). By default + compat_mode is disabled. + +
+
Dependencies
@@ -271,6 +286,26 @@ modparam("cgrates", "max_async_connections", 20) ... modparam("cgrates", "retry_timeout", 120) ... + + +
+
+ <varname>compat_mode</varname> (integer) + + Indicates whether OpenSIPS should use the old (compat_mode) + CGRateS version API (pre-rc8). + + + + Default value is false (0). + + + + Set <varname>compat_mode</varname> parameter + +... +modparam("cgrates", "compat_mode", 1) +...
@@ -542,7 +577,7 @@ modparam("cgrates", "retry_timeout", 120) ... # cgrates_auth("$fU", "$rU"); simulation - $cgr(Tenant) = $fd; + $cgr_opt(Tenant) = $fd; $cgr(Account) = $fU; $cgr(OriginID) = $ci; $cgr(SetupTime) = "" + $Ts; @@ -593,7 +628,7 @@ modparam("cgrates", "retry_timeout", 120) ... if (!has_totag()) { ... - $cgr(Tenant) = $fd; # set the From domain as a tenant + $cgr_opt(Tenant) = $fd; # set the From domain as a tenant $cgr(RequestType) = "*prepaid"; # do prepaid accounting if (!cgrates_auth("$fU", "$rU")) { sl_send_reply("403", "Forbidden"); @@ -610,7 +645,7 @@ modparam("cgrates", "retry_timeout", 120) if (!has_totag()) { ... # first session - authorize the user - $cgr(Tenant) = $fd; # set the From domain as a tenant + $cgr_opt(Tenant) = $fd; # set the From domain as a tenant $cgr(RequestType) = "*prepaid"; # do prepaid accounting if (!cgrates_auth("$fU", "$rU")) { sl_send_reply("403", "Forbidden"); @@ -618,7 +653,7 @@ modparam("cgrates", "retry_timeout", 120) } # second session - authorize the carrier - $(cgr(Tenant)[carrier]) = $td; + $(cgr_opt(Tenant)[carrier]) = $td; $(cgr(RequestType)[carrier]) = "*postpaid"; if (!cgrates_auth("$tU", "$fU", "carrier")) { # use a different carrier @@ -633,6 +668,51 @@ modparam("cgrates", "retry_timeout", 120)
+
+ <varname>$cgr_opt(name) / $(cgr_opt(name)[session])</varname> + + Used to tune the request parameter of a CGRateS request when used in + non-compat_mode. + + + Note: for all request options integer values act as + boolean values: 0 disables the feature and + 1(or different than 0 value) enables it. String + variables are passed just as they are set. + + + Possible values at the time the documentation was written: + + + Tenant - tune CGRateS Tenant. + + + GetAttributes - requests the account + attributes from the CGRateS DB. + + + GetMaxUsage - request the maximum time + the call is allowed to run. + + + GetSuppliers - request an array with + all the suppliers for that can terminate that call. + + + + + $cgr_opt(name) usage + + ... + $cgr_opt(Tenant) = "cgrates.org"; + $cgr_opt(GetMaxUsage) = 1; # also retrieve the max usage + if (!cgrates_auth("$fU", "$rU")) { + # call rejected + } + ... + + +
<varname>$cgrret</varname> @@ -771,8 +851,7 @@ route [auth_reply] 1 - successful call - the CGRateS account is allowed to make the call. - - + -1 - &osips; returned an internal error (i.e. server is out of memory). @@ -789,7 +868,7 @@ route [auth_reply] - <function moreinfo="none">async cgrates_cmd</function> usage + <function moreinfo="none">async cgrates_cmd compat_mode</function> usage route { ... @@ -802,6 +881,31 @@ route { async(cgrates_cmd("SMGenericV1.GetMaxUsage"), auth_reply); } +route [auth_reply] +{ + if ($rc < 0) { + xlog("Call not authorized: code=$cgrret!\n"); + send_reply("403", "Forbidden"); + exit; + } + ... +} + + + + <function moreinfo="none">async cgrates_cmd new</function> usage + +route { + ... + $cgr_opt(Tenant) = $fd; + $cgr(Account) = $fU; + $cgr(OriginID) = $ci; + $cgr(SetupTime) = "" + $Ts; + $cgr(RequestType) = "*prepaid"; + $cgr(Destination) = $rU; + async(cgrates_cmd("SessionSv1.AuthorizeEventWithDigest"), auth_reply); +} + route [auth_reply] { if ($rc < 0) { From b0aeb0aef8c847e9ccb41f97e8813d2178854955 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 15 Mar 2018 18:45:45 +0200 Subject: [PATCH 02/10] [WIP] cgrates: parse values back for Auth --- modules/cgrates/cgrates_acc.c | 38 +++---- modules/cgrates/cgrates_auth.c | 60 +++++++---- modules/cgrates/cgrates_common.c | 170 ++++++++++++++++++++++++++++--- modules/cgrates/cgrates_common.h | 18 +++- modules/cgrates/cgrates_engine.h | 1 - 5 files changed, 230 insertions(+), 57 deletions(-) diff --git a/modules/cgrates/cgrates_acc.c b/modules/cgrates/cgrates_acc.c index 8a50e09e25..179eececc2 100644 --- a/modules/cgrates/cgrates_acc.c +++ b/modules/cgrates/cgrates_acc.c @@ -340,7 +340,7 @@ static json_object *cgr_get_start_acc_msg(struct sip_msg *msg, goto error; } else { callid = cgr_get_sess_callid(msg, s, &msg->callid->body); - if (!callid || cgr_msg_push_str(cmsg, "OriginID", callid) < 0) { + if (!callid || cgr_obj_push_str(cmsg->params, "OriginID", callid) < 0) { LM_ERR("cannot push OriginID!\n"); goto error; } @@ -348,26 +348,26 @@ static json_object *cgr_get_start_acc_msg(struct sip_msg *msg, } if (ctx && !cgr_get_const_kv(&s->event_kvs, "DialogID") && - cgr_msg_push_int(cmsg, "DialogID", dlg->h_id) < 0) { + cgr_obj_push_int(cmsg->params, "DialogID", dlg->h_id) < 0) { LM_ERR("cannot push DialogID!\n"); goto error; } if (ctx && !cgr_get_const_kv(&s->event_kvs, "DialogEntry") && - cgr_msg_push_int(cmsg, "DialogEntry", dlg->h_entry) < 0) { + cgr_obj_push_int(cmsg->params, "DialogEntry", dlg->h_entry) < 0) { LM_ERR("cannot push DialogEntry!\n"); goto error; } /* Account */ - if (cgr_msg_push_str(cmsg, "Account", &si->acc) < 0) { + if (cgr_obj_push_str(cmsg->params, "Account", &si->acc) < 0) { LM_ERR("cannot push Account info!\n"); goto error; } /* SetupTime */ stime.s = int2str(si->start_time, &stime.len); - if (cgr_msg_push_str(cmsg, "SetupTime", &stime) < 0) { + if (cgr_obj_push_str(cmsg->params, "SetupTime", &stime) < 0) { LM_ERR("cannot push SetupTime info!\n"); goto error; } @@ -375,14 +375,14 @@ static json_object *cgr_get_start_acc_msg(struct sip_msg *msg, /* AnswerTime */ if (ctx) { stime.s = int2str(ctx->answer_time, &stime.len); - if (cgr_msg_push_str(cmsg, "AnswerTime", &stime) < 0) { + if (cgr_obj_push_str(cmsg->params, "AnswerTime", &stime) < 0) { LM_ERR("cannot push AnswerTime info!\n"); goto error; } } /* Destination */ - if (cgr_msg_push_str(cmsg, "Destination", &si->dst) < 0) { + if (cgr_obj_push_str(cmsg->params, "Destination", &si->dst) < 0) { LM_ERR("cannot push Destination info!\n"); goto error; } @@ -428,14 +428,14 @@ static json_object *cgr_get_stop_acc_msg(struct sip_msg *msg, /* if origin was not added from script, add it now */ if (ctx && !cgr_get_const_kv(&s->event_kvs, "OriginID")) { callid = cgr_get_sess_callid(msg, s, &dlg->callid); - if (cgr_msg_push_str(cmsg, "OriginID", callid) < 0) { + if (cgr_obj_push_str(cmsg->params, "OriginID", callid) < 0) { LM_ERR("cannot push OriginID!\n"); goto error; } } /* Account */ - if (cgr_msg_push_str(cmsg, "Account", &si->acc) < 0) { + if (cgr_obj_push_str(cmsg->params, "Account", &si->acc) < 0) { LM_ERR("cannot push Account info!\n"); goto error; } @@ -443,7 +443,7 @@ static json_object *cgr_get_stop_acc_msg(struct sip_msg *msg, /* SetupTime */ if (ctx->answer_time != si->start_time) { tmp.s = int2str(si->start_time, &tmp.len); - if (cgr_msg_push_str(cmsg, "SetupTime", &tmp) < 0) { + if (cgr_obj_push_str(cmsg->params, "SetupTime", &tmp) < 0) { LM_ERR("cannot push SetupTime info!\n"); goto error; } @@ -451,7 +451,7 @@ static json_object *cgr_get_stop_acc_msg(struct sip_msg *msg, /* AnswerTime */ tmp.s = int2str(ctx->answer_time, &tmp.len); - if (cgr_msg_push_str(cmsg, "AnswerTime", &tmp) < 0) { + if (cgr_obj_push_str(cmsg->params, "AnswerTime", &tmp) < 0) { LM_ERR("cannot push AnswerTime info!\n"); goto error; } @@ -461,7 +461,7 @@ static json_object *cgr_get_stop_acc_msg(struct sip_msg *msg, tmp.s[tmp.len] = 's'; tmp.len++; tmp.s[tmp.len] = 0; - if (cgr_msg_push_str(cmsg, "Usage", &tmp) < 0) { + if (cgr_obj_push_str(cmsg->params, "Usage", &tmp) < 0) { LM_ERR("cannot add Usage node\n"); goto error; } @@ -489,17 +489,17 @@ static json_object *cgr_get_cdr_acc_msg(struct sip_msg *msg, return NULL; } - if (cgr_msg_push_str(cmsg, "OriginID", callid) < 0) { + if (cgr_obj_push_str(cmsg->params, "OriginID", callid) < 0) { LM_ERR("cannot add OriginID node\n"); goto error; } - if (cgr_msg_push_str(cmsg, "Account", &si->acc) < 0) { + if (cgr_obj_push_str(cmsg->params, "Account", &si->acc) < 0) { LM_ERR("cannot add Account node\n"); goto error; } - if (cgr_msg_push_str(cmsg, "Destination", &si->dst) < 0) { + if (cgr_obj_push_str(cmsg->params, "Destination", &si->dst) < 0) { LM_ERR("cannot add Destination node\n"); goto error; } @@ -509,14 +509,14 @@ static json_object *cgr_get_cdr_acc_msg(struct sip_msg *msg, tmp.s[tmp.len] = 's'; tmp.len++; tmp.s[tmp.len] = 0; - if (cgr_msg_push_str(cmsg, "Usage", &tmp) < 0) { + if (cgr_obj_push_str(cmsg->params, "Usage", &tmp) < 0) { LM_ERR("cannot add Usage node\n"); goto error; } if (ctx->answer_time) { tmp.s = int2str(ctx->answer_time, &tmp.len); - if (cgr_msg_push_str(cmsg, "AnswerTime", &tmp) < 0) { + if (cgr_obj_push_str(cmsg->params, "AnswerTime", &tmp) < 0) { LM_ERR("cannot add AnswerTime node\n"); goto error; } @@ -524,13 +524,13 @@ static json_object *cgr_get_cdr_acc_msg(struct sip_msg *msg, if (si->start_time && si->start_time != ctx->answer_time) { tmp.s = int2str(si->start_time, &tmp.len); - if (cgr_msg_push_str(cmsg, "SetupTime", &tmp) < 0) { + if (cgr_obj_push_str(cmsg->params, "SetupTime", &tmp) < 0) { LM_ERR("cannot add SetupTime node\n"); goto error; } } - if (dlg && cgr_msg_push_str(cmsg, "DisconnectCause", &dlg->terminate_reason) < 0) { + if (dlg && cgr_obj_push_str(cmsg->params, "DisconnectCause", &dlg->terminate_reason) < 0) { LM_ERR("cannot add DisconnectCause node\n"); goto error; } diff --git a/modules/cgrates/cgrates_auth.c b/modules/cgrates/cgrates_auth.c index 4655241c86..a3a6eaa6a7 100644 --- a/modules/cgrates/cgrates_auth.c +++ b/modules/cgrates/cgrates_auth.c @@ -25,6 +25,7 @@ static int cgr_proc_auth_reply(struct cgr_conn *c, json_object *jobj, void *p, char *error) { + struct cgr_kv *k; int_str val; if (error) { val.s.s = error; @@ -36,29 +37,45 @@ static int cgr_proc_auth_reply(struct cgr_conn *c, json_object *jobj, return -2; } - if (json_object_get_type(jobj) != json_type_int) { - LM_ERR("CGRateS returned a non-int type in Auth reply: %d %s\n", - json_object_get_type(jobj), json_object_to_json_string(jobj)); - return -5; + if (cgre_compat_mode) { + if (json_object_get_type(jobj) != json_type_int) { + LM_ERR("CGRateS returned a non-int type in Auth reply: %d %s\n", + json_object_get_type(jobj), json_object_to_json_string(jobj)); + return -5; + } + val.n = json_object_get_int(jobj); + /* -1: always allowed (postpaid) + * 0: not allowed to call + * *: allowed + */ + if (cgrates_set_reply(CGR_KVF_TYPE_INT, &val) < 0) { + LM_ERR("cannot set the reply value!\n"); + return -1; + } + return ((val.n == 0) ? -2: 1); + } else if (cgrates_set_reply_with_values(jobj) < 0) { + LM_ERR("cannot set the reply values!\n"); + return -1; } - val.n = json_object_get_int(jobj); - /* -1: always allowed (postpaid) - * 0: not allowed to call - * *: allowed - */ - if (cgrates_set_reply(CGR_KVF_TYPE_INT, &val) < 0) { - LM_ERR("cannot set the reply value!\n"); + val.s.s = "MaxUsage"; + val.s.len = strlen(val.s.s); + k = cgr_get_local(val.s); + if (!k || !(k->flags & CGR_KVF_TYPE_INT)) { + LM_ERR("MaxUsage not found in command reply!\n"); return -1; } - return ((val.n == 0) ? -2: 1); + + return ((k->value.n == 0) ? -2: 1); } static json_object *cgr_get_auth_msg(struct sip_msg *msg, str *acc, str *dst, str *tag) { struct cgr_session *s; struct cgr_msg *cmsg = NULL; - static str cmd = str_init("SMGenericV1.GetMaxUsage"); + static str cmd_ng = str_init("SessionSv1.AuthorizeEvent"); + static str cmd_compat = str_init("SMGenericV1.GetMaxUsage"); str stime; + str *cmd = (cgre_compat_mode ? &cmd_compat: &cmd_ng); if (msg->callid==NULL && ((parse_headers(msg, HDR_CALLID_F, 0)==-1) || (msg->callid==NULL)) ) { @@ -68,34 +85,41 @@ static json_object *cgr_get_auth_msg(struct sip_msg *msg, str *acc, str *dst, st s = cgr_get_sess(cgr_try_get_ctx(), tag); stime.s = int2str(time(NULL), &stime.len); - cmsg = cgr_get_generic_msg(&cmd, s); + cmsg = cgr_get_generic_msg(cmd, s); if (!cmsg) { LM_ERR("cannot create generic cgrates message!\n"); return NULL; } + if (!cgre_compat_mode && + ((s && !cgr_get_const_kv(&s->req_kvs, "GetMaxUsage")) || !s) && + cgr_obj_push_bool(cmsg->opts, "GetMaxUsage", 1) < 0) { + LM_ERR("cannot push GetMaxUsage to request opts!\n"); + goto error; + } + /* OriginID */ /* if origin was not added from script, add it now */ if (((s && !cgr_get_const_kv(&s->event_kvs, "OriginID")) || !s) && - cgr_msg_push_str(cmsg, "OriginID", &msg->callid->body) < 0) { + cgr_obj_push_str(cmsg->params, "OriginID", &msg->callid->body) < 0) { LM_ERR("cannot push OriginID!\n"); goto error; } /* Account */ - if (cgr_msg_push_str(cmsg, "Account", acc) < 0) { + if (cgr_obj_push_str(cmsg->params, "Account", acc) < 0) { LM_ERR("cannot push Account info!\n"); goto error; } /* SetupTime */ - if (cgr_msg_push_str(cmsg, "SetupTime", &stime) < 0) { + if (cgr_obj_push_str(cmsg->params, "SetupTime", &stime) < 0) { LM_ERR("cannot push SetupTime info!\n"); goto error; } /* Destination */ - if (cgr_msg_push_str(cmsg, "Destination", dst) < 0) { + if (cgr_obj_push_str(cmsg->params, "Destination", dst) < 0) { LM_ERR("cannot push Destination info!\n"); goto error; } diff --git a/modules/cgrates/cgrates_common.c b/modules/cgrates/cgrates_common.c index cd4cbe9f95..defbbb7b15 100644 --- a/modules/cgrates/cgrates_common.c +++ b/modules/cgrates/cgrates_common.c @@ -107,7 +107,16 @@ void cgr_free_kv(struct cgr_kv *kv) shm_free(kv); } -/* message builder */ +/* handle local ctx */ + +/* local kvs are stored in pkg, whereas session kvs are stored in shm */ +struct cgr_kv *cgr_get_local(str key) +{ + struct cgr_local_ctx *ctx = CGR_GET_LOCAL_CTX(); + if (!ctx) + return NULL; + return cgr_get_kv(&ctx->kvs, key); +} int cgrates_set_reply(int type, int_str *value) { @@ -126,6 +135,7 @@ int cgrates_set_reply(int type, int_str *value) return -1; } memset(ctx, 0, sizeof(*ctx)); + INIT_LIST_HEAD(&ctx->kvs); CGR_PUT_LOCAL_CTX(ctx); LM_DBG("new local ctx=%p\n", ctx); } @@ -148,6 +158,93 @@ int cgrates_set_reply(int type, int_str *value) return 0; } +static int cgr_add_local(struct list_head *list, + const char *key, int_str val, int flags) +{ + int klen; + struct cgr_kv *kv; + + klen = strlen(key); + kv = pkg_malloc(sizeof *kv + klen + + (flags & CGR_KVF_TYPE_STR ? val.s.len: 0)); + if (!kv) { + LM_ERR("no more pkgmem for new %s kv!\n", key); + return -1; + } + memset(kv, 0, sizeof *kv); + kv->flags = flags; + kv->key.s = (char *)(kv + 1); + kv->key.len = klen; + memcpy(kv->key.s, key, klen); + if (flags & CGR_KVF_TYPE_STR) { + kv->value.s.s = kv->key.s + kv->key.len; + kv->value.s.len = val.s.len; + memcpy(kv->value.s.s, val.s.s, val.s.len); + } else + kv->value.n = val.n; + list_add(&kv->list, list); + LM_DBG("created new local key %.*s\n", kv->key.len, kv->key.s); + return 0; +} + +void cgr_free_local_kv(struct cgr_kv *kv) +{ + list_del(&kv->list); + pkg_free(kv); +} + +int cgrates_set_reply_with_values(json_object *jobj) +{ + int_str val; + struct cgr_local_ctx *ctx; + + /* here, we pass all the nodes in the list */ + val.s.s = (char *)json_object_to_json_string(jobj); + val.s.len = strlen(val.s.s); + if (cgrates_set_reply(CGR_KVF_TYPE_STR, &val) < 0) + return -1; + ctx = CGR_GET_LOCAL_CTX(); + + if (!ctx) { + LM_BUG("local ctx not found but reply set\n"); + return -1; + } + + json_object_object_foreach(jobj, k, v) { + switch (json_object_get_type(v)) { + case json_type_null: + continue; + case json_type_boolean: + case json_type_int: + if (json_object_get_type(v) == json_type_int) + val.n = json_object_get_int(v); + else if (json_object_get_boolean(v)) + val.n = 1; + else + val.n = 0; + if (cgr_add_local(&ctx->kvs, k, val, CGR_KVF_TYPE_INT) < 0) { + LM_ERR("cannot add integer kv!\n"); + return -1; + } + break; + + case json_type_string: + case json_type_double: + case json_type_object: + case json_type_array: + val.s.s = (char *)json_object_to_json_string(jobj); + val.s.len = strlen(val.s.s); + if (cgr_add_local(&ctx->kvs, k, val, CGR_KVF_TYPE_STR) < 0) { + LM_ERR("cannot add string kv!\n"); + return -1; + } + break; + } + } + return 0; +} + +/* message builder */ static int cgr_id_index = 0; int cgr_init_common(void) @@ -163,7 +260,6 @@ int cgr_init_common(void) return 0; } - static inline int cgr_unique_id(void) { cgr_id_index += (1 << sizeof(unsigned short)); @@ -202,6 +298,33 @@ struct cgr_msg *cgr_get_generic_msg(str *method, struct cgr_session *s) JSON_CHECK(cmsg.params = json_object_new_object(), "params object"); json_object_array_add(jarr, cmsg.params); + if (!cgre_compat_mode) { + if (s) { + list_for_each(l, &s->req_kvs) { + kv = list_entry(l, struct cgr_kv, list); + if (kv->flags & CGR_KVF_TYPE_NULL) { + jtmp = NULL; + } else if (kv->flags & CGR_KVF_TYPE_INT) { + /* XXX: we treat here int values as booleans */ + jtmp = json_object_new_boolean(kv->value.n); + JSON_CHECK(jtmp, kv->key.s); + } else { + jtmp = json_object_new_string_len(kv->value.s.s, kv->value.s.len); + JSON_CHECK(jtmp, kv->key.s); + } + json_object_object_add(cmsg.params, kv->key.s, jtmp); + } + } + + /* TODO: check if there is already an Event in the opts? */ + /* in non-compat mode, event kvs go into the event object */ + cmsg.opts = cmsg.params; + jtmp = json_object_new_object(); + JSON_CHECK(jtmp, "Event"); + json_object_object_add(cmsg.params, "Event", jtmp); + cmsg.params = jtmp; + } + if (s) { list_for_each(l, &s->event_kvs) { kv = list_entry(l, struct cgr_kv, list); @@ -224,23 +347,34 @@ struct cgr_msg *cgr_get_generic_msg(str *method, struct cgr_session *s) return NULL; } -int cgr_msg_push_str(struct cgr_msg *cmsg, const char *key, str *value) +int cgr_obj_push_str(json_object *msg, const char *key, str *value) { json_object *jmsg; jmsg = json_object_new_string_len(value->s, value->len); JSON_CHECK(jmsg, key); - json_object_object_add(cmsg->params, key, jmsg); + json_object_object_add(msg, key, jmsg); return 0; error: return -1; } -int cgr_msg_push_int(struct cgr_msg *cmsg, const char *key, unsigned int value) +int cgr_obj_push_int(json_object *msg, const char *key, unsigned int value) { json_object *jmsg; jmsg = json_object_new_int(value); JSON_CHECK(jmsg, key); - json_object_object_add(cmsg->params, key, jmsg); + json_object_object_add(msg, key, jmsg); + return 0; +error: + return -1; +} + +int cgr_obj_push_bool(json_object *msg, const char *key, int value) +{ + json_object *jmsg; + jmsg = json_object_new_boolean(value); + JSON_CHECK(jmsg, key); + json_object_object_add(msg, key, jmsg); return 0; error: return -1; @@ -361,14 +495,23 @@ struct cgr_session *cgr_get_sess_new(struct cgr_ctx *ctx, str *tag) return s; } +static void _cgr_free_local_ctx(struct cgr_local_ctx *ctx) +{ + struct list_head *l, *t; + LM_DBG("release local ctx=%p\n", ctx); + if (ctx->reply) { + pkg_free(ctx->reply); + ctx->reply = 0; + } + list_for_each_safe(l, t, &ctx->kvs) + cgr_free_local_kv(list_entry(l, struct cgr_kv, list)); +} + #define CGR_RESET_REPLY_CTX() \ do { \ struct cgr_local_ctx *_c = CGR_GET_LOCAL_CTX(); \ - if (_c) {\ - if (_c->reply) \ - pkg_free(_c->reply); \ - _c->reply = 0; \ - } \ + if (_c) \ + _cgr_free_local_ctx(_c); \ } while (0) /* CGR logic */ @@ -827,13 +970,12 @@ void cgr_move_ctx( struct cell* t, int type, struct tmcb_params *ps) CGR_PUT_CTX(NULL); } + /* function that removes local context */ void cgr_free_local_ctx(void *param) { struct cgr_local_ctx *ctx = (struct cgr_local_ctx *)param; - LM_DBG("release local ctx=%p\n", ctx); - if (ctx->reply) - pkg_free(ctx->reply); + _cgr_free_local_ctx(ctx); pkg_free(ctx); } diff --git a/modules/cgrates/cgrates_common.h b/modules/cgrates/cgrates_common.h index 1091203e2e..24cd9370c2 100644 --- a/modules/cgrates/cgrates_common.h +++ b/modules/cgrates/cgrates_common.h @@ -72,6 +72,7 @@ struct cgr_ctx { struct cgr_local_ctx { /* reply status */ unsigned reply_flags; + struct list_head kvs; int_str *reply; }; @@ -91,17 +92,26 @@ struct cgr_conn { struct cgr_msg { json_object *msg; + json_object *opts; json_object *params; }; /* init common variables */ +extern int cgre_compat_mode; int cgr_init_common(void); /* message builder */ -int cgrates_set_reply(int type, int_str *value); struct cgr_msg *cgr_get_generic_msg(str *method, struct cgr_session *sess); -int cgr_msg_push_str(struct cgr_msg *cmsg, const char *key, str *value); -int cgr_msg_push_int(struct cgr_msg *cmsg, const char *key, unsigned int value); +int cgr_obj_push_str(json_object *msg, const char *key, str *value); +int cgr_obj_push_int(json_object *msg, const char *key, unsigned int value); +int cgr_obj_push_bool(json_object *msg, const char *key, int value); + +/* handle local ctx */ +extern int cgr_ctx_local_idx; +void cgr_free_local_ctx(void *param); +int cgrates_set_reply(int type, int_str *value); +int cgrates_set_reply_with_values(json_object *msg); +struct cgr_kv *cgr_get_local(str key); /* key-value manipulation */ struct cgr_kv *cgr_new_kv(str key); @@ -118,13 +128,11 @@ struct cgr_session *cgr_get_sess_new(struct cgr_ctx *ctx, str *name); /* context manipulation */ extern int cgr_ctx_idx; -extern int cgr_ctx_local_idx; extern int cgr_tm_ctx_idx; struct cgr_ctx *cgr_get_ctx_new(void); struct cgr_ctx *cgr_get_ctx(void); struct cgr_ctx *cgr_try_get_ctx(void); void cgr_free_ctx(void *param); -void cgr_free_local_ctx(void *param); void cgr_move_ctx( struct cell* t, int type, struct tmcb_params *ps); #define CGR_GET_CTX() ((struct cgr_ctx *)context_get_ptr(CONTEXT_GLOBAL, \ diff --git a/modules/cgrates/cgrates_engine.h b/modules/cgrates/cgrates_engine.h index 36bca10495..c23873be1a 100644 --- a/modules/cgrates/cgrates_engine.h +++ b/modules/cgrates/cgrates_engine.h @@ -51,7 +51,6 @@ extern struct list_head cgrates_engines; #define CGRC_IS_DEFAULT(_c) ((_c)->flags & CGRF_DEFAULT) #define CGRC_SET_DEFAULT(_c) (_c)->flags |= CGRF_DEFAULT -extern int cgre_compat_mode; extern int cgre_retry_tout; extern int cgrc_max_conns; extern str cgre_bind_ip; From 2bfd0a315b60dd1946152ccc6167b970450c2b79 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Wed, 21 Mar 2018 16:45:19 +0200 Subject: [PATCH 03/10] cgrates: add names for the $cgrret var --- modules/cgrates/cgrates.c | 69 +++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/modules/cgrates/cgrates.c b/modules/cgrates/cgrates.c index b52d837516..01f0bdafb8 100644 --- a/modules/cgrates/cgrates.c +++ b/modules/cgrates/cgrates.c @@ -40,6 +40,11 @@ #include "cgrates_common.h" #include "cgrates_engine.h" + +#define CGR_PV_NAME_NONE 0 /* used to determine if a name was not set */ +#define CGR_PV_NAME_STR 1 +#define CGR_PV_NAME_VAR 2 + int cgre_compat_mode = 0; int cgre_retry_tout = CGR_DEFAULT_RETRY_TIMEOUT; int cgrc_max_conns = CGR_DEFAULT_MAX_CONNS; @@ -105,8 +110,8 @@ static pv_export_t pvars[] = { pv_parse_cgr, pv_parse_idx_cgr, 0, 0}, { str_init("cgr_opt"), 2004, w_pv_get_cgr_opt, w_pv_set_cgr_opt, w_pv_parse_cgr, pv_parse_idx_cgr, 0, 0}, - { str_init("cgrret"), 2005, pv_get_cgr_reply, - 0, 0, 0, 0, 0}, + { str_init("cgrret"), 2005, pv_get_cgr_reply, 0, + pv_parse_cgr, 0, 0, 0}, { {0, 0}, 0, 0, 0, 0, 0, 0, 0 } }; @@ -359,7 +364,7 @@ static inline str *pv_get_idx_value(struct sip_msg *msg, pv_param_t *param) static pv_value_t idx_val; if (param->pvi.u.dval) { - if (param->pvi.type == PV_NAME_PVAR) { + if (param->pvi.type == CGR_PV_NAME_VAR) { if (pv_get_spec_value(msg, (pv_spec_p)param->pvi.u.dval, &idx_val) != 0) { LM_WARN("cannot get the tag of the cgr variable! " "using default\n"); @@ -394,7 +399,7 @@ static int pv_set_cgr(struct sip_msg *msg, pv_param_t *param, } /* first get the name of the field */ - if (param->pvn.type == PV_NAME_PVAR) { + if (param->pvn.type == CGR_PV_NAME_VAR) { if (pv_get_spec_value(msg, (pv_spec_p)param->pvn.u.dname, &name_val) != 0) { LM_ERR("cannot get the name of the cgr variable\n"); return -1; @@ -500,7 +505,7 @@ static int pv_get_cgr(struct sip_msg *msg, pv_param_t *param, return pv_get_null(msg, param, val); /* first get the name of the field */ - if (param->pvn.type == PV_NAME_PVAR) { + if (param->pvn.type == CGR_PV_NAME_VAR) { if (pv_get_spec_value(msg, (pv_spec_p)param->pvn.u.dname, &name_val) != 0) { LM_ERR("cannot get the name of the cgr variable\n"); return -1; @@ -522,7 +527,7 @@ static int pv_get_cgr(struct sip_msg *msg, pv_param_t *param, return pv_get_null(msg, param, val); if (kv->flags & CGR_KVF_TYPE_INT) { - val->rs.s = int2str(kv->value.n, &val->rs.len); + val->rs.s = sint2str(kv->value.n, &val->rs.len); val->flags = PV_VAL_STR|PV_VAL_INT|PV_TYPE_INT; } else if (kv->flags & CGR_KVF_TYPE_STR) { val->rs = kv->value.s; @@ -549,6 +554,8 @@ static int w_pv_get_cgr_opt(struct sip_msg *msg, pv_param_t *param, static int pv_get_cgr_reply(struct sip_msg *msg, pv_param_t *param, pv_value_t *val) { + str tmp; + struct cgr_kv *kv; struct cgr_local_ctx *ctx; if (!param || !val) { @@ -559,13 +566,41 @@ static int pv_get_cgr_reply(struct sip_msg *msg, pv_param_t *param, if (!(ctx = CGR_GET_LOCAL_CTX()) || !ctx->reply) return pv_get_null(msg, param, val); - if (ctx->reply_flags & CGR_KVF_TYPE_STR) { - val->rs = ctx->reply->s; - val->flags = PV_VAL_STR; + if (param->pvn.type == CGR_PV_NAME_NONE) { + if (ctx->reply_flags & CGR_KVF_TYPE_STR) { + val->rs = ctx->reply->s; + val->flags = PV_VAL_STR; + } else { + val->ri = ctx->reply->n; + val->flags = PV_VAL_INT|PV_TYPE_INT; + } } else { - val->rs.s = int2str(ctx->reply->n, &val->rs.len); - val->ri = ctx->reply->n; - val->flags = PV_VAL_STR|PV_VAL_INT|PV_TYPE_INT; + if (param->pvn.type == CGR_PV_NAME_VAR) { + if (pv_get_spec_value(msg, (pv_spec_p)param->pvn.u.dname, val) != 0) { + LM_ERR("cannot get the name of the $cgrret variable\n"); + return -1; + } + if (val->flags & PV_VAL_NULL || !(val->flags & PV_VAL_STR)) { + LM_ERR("invalid name for the $cgrret variable!\n"); + return -1; + } + tmp = val->rs; + } else + tmp = param->pvn.u.isname.name.s; + kv = cgr_get_local(tmp); + if (!kv) + return pv_get_null(msg, param, val); + if (kv->flags & CGR_KVF_TYPE_STR) { + val->rs = kv->value.s; + val->flags = PV_VAL_STR; + } else { + val->ri = kv->value.n; + val->flags = PV_VAL_INT|PV_TYPE_INT; + } + } + if (val->flags & PV_VAL_INT) { + val->rs.s = sint2str(val->ri, &val->rs.len); + val->flags |= PV_VAL_STR; } return 0; @@ -590,8 +625,8 @@ static int pv_parse_cgr(pv_spec_p sp, str *in) in->len, in->s); return -1; } - sp->pvp.pvn.u.dname = sp; - sp->pvp.pvn.type = PV_NAME_PVAR; + sp->pvp.pvn.u.dname = pv; + sp->pvp.pvn.type = CGR_PV_NAME_VAR; } else { /* we need to add the null terminator */ s = pkg_malloc(in->len + 1); @@ -604,7 +639,7 @@ static int pv_parse_cgr(pv_spec_p sp, str *in) sp->pvp.pvn.u.isname.name.s.s = s; sp->pvp.pvn.u.isname.name.s.len = in->len; - sp->pvp.pvn.type = PV_NAME_INTSTR; + sp->pvp.pvn.type = CGR_PV_NAME_STR; } return 0; } @@ -640,7 +675,7 @@ static int pv_parse_idx_cgr(pv_spec_p sp, str *in) return -1; } sp->pvp.pvi.u.dval = sp; - sp->pvp.pvi.type = PV_NAME_PVAR; + sp->pvp.pvi.type = CGR_PV_NAME_VAR; } else { /* we need to add the null terminator */ s = pkg_malloc(sizeof(str) + in->len); @@ -653,7 +688,7 @@ static int pv_parse_idx_cgr(pv_spec_p sp, str *in) s->len = in->len; sp->pvp.pvi.u.dval = s; - sp->pvp.pvi.type = PV_NAME_INTSTR; + sp->pvp.pvi.type = CGR_PV_NAME_STR; } return 0; } From 6325370e85e68a7b595e0ff6e99008e9619811ae Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Wed, 21 Mar 2018 16:50:18 +0200 Subject: [PATCH 04/10] cgrates: document the new $cgrret(name) usage --- modules/cgrates/README | 27 ++++++++++++++++++--------- modules/cgrates/doc/cgrates_admin.xml | 21 ++++++++++++++------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/modules/cgrates/README b/modules/cgrates/README index b64865f1b1..7b6f75f889 100644 --- a/modules/cgrates/README +++ b/modules/cgrates/README @@ -44,7 +44,7 @@ Razvan Crainea 1.10.1. $cgr(name) / $(cgr(name)[session]) 1.10.2. $cgr_opt(name) / $(cgr_opt(name)[session]) - 1.10.3. $cgrret + 1.10.3. $cgrret(name) 1.11. Exported Asynchronous Functions @@ -66,7 +66,7 @@ Razvan Crainea 1.9. $cgr(name) simple usage 1.10. $cgr(name) multiple sessions usage 1.11. $cgr_opt(name) usage - 1.12. $cgrret usage + 1.12. $cgrret(name) usage 1.13. async cgrates_auth usage 1.14. async cgrates_cmd compat_mode usage 1.15. async cgrates_cmd new usage @@ -173,8 +173,9 @@ ompat mode */ $cgr(SetupTime) = "" + $Ts; $cgr(RequestType) = "*prepaid"; $cgr(Destination) = $rU; - cgrates_cmd("SMGenericV1.GetMaxUsage"); - xlog("Call is allowed to run $cgrret seconds\n"); + cgrates_cmd("SessionSv1.AuthorizeEvent"); + xlog("Call is allowed to run $cgrret(MaxUsage) seconds\n +"); ... 1.5. CGRateS Failover @@ -428,7 +429,7 @@ modparam("cgrates", "compat_mode", 1) $cgr(SetupTime) = "" + $Ts; $cgr(RequestType) = "*prepaid"; $cgr(Destination) = $rU; - cgrates_cmd("SMGenericV1.GetMaxUsage"); + cgrates_cmd("SessionSv1.AuthorizeEvent"); xlog("Call is allowed to run $cgrret seconds\n"); ... @@ -525,14 +526,22 @@ ounting } ... -1.10.3. $cgrret +1.10.3. $cgrret(name) - Returns the reply message of a CGRateS command in script. + Returns the reply message of a CGRateS command in script, or + when used in the non-compat mode, one of the objects within the + reply. - Example 1.12. $cgrret usage + Example 1.12. $cgrret(name) usage ... cgrates_auth("$fU", "$rU"); + + # in compat mode xlog("Call is allowed to run $cgrret seconds\n"); + + # in non-compat mode + xlog("Call is allowed to run $cgrret(MaxUsage) seconds\n +"); ... 1.11. Exported Asynchronous Functions @@ -614,7 +623,7 @@ route { $cgr(SetupTime) = "" + $Ts; $cgr(RequestType) = "*prepaid"; $cgr(Destination) = $rU; - async(cgrates_cmd("SMGenericV1.GetMaxUsage"), auth_reply); + async(cgrates_cmd("SessionSv1.AuthorizeEvent"), auth_reply); } route [auth_reply] diff --git a/modules/cgrates/doc/cgrates_admin.xml b/modules/cgrates/doc/cgrates_admin.xml index 96396543ff..bfec78a102 100644 --- a/modules/cgrates/doc/cgrates_admin.xml +++ b/modules/cgrates/doc/cgrates_admin.xml @@ -122,8 +122,8 @@ $cgr(SetupTime) = "" + $Ts; $cgr(RequestType) = "*prepaid"; $cgr(Destination) = $rU; - cgrates_cmd("SMGenericV1.GetMaxUsage"); - xlog("Call is allowed to run $cgrret seconds\n"); + cgrates_cmd("SessionSv1.AuthorizeEvent"); + xlog("Call is allowed to run $cgrret(MaxUsage) seconds\n"); ... @@ -583,7 +583,7 @@ modparam("cgrates", "compat_mode", 1) $cgr(SetupTime) = "" + $Ts; $cgr(RequestType) = "*prepaid"; $cgr(Destination) = $rU; - cgrates_cmd("SMGenericV1.GetMaxUsage"); + cgrates_cmd("SessionSv1.AuthorizeEvent"); xlog("Call is allowed to run $cgrret seconds\n"); ... @@ -714,16 +714,23 @@ modparam("cgrates", "compat_mode", 1)
- <varname>$cgrret</varname> + <varname>$cgrret(name)</varname> - Returns the reply message of a CGRateS command in script. + Returns the reply message of a CGRateS command in script, + or when used in the non-compat mode, one of the objects + within the reply. - $cgrret usage + $cgrret(name) usage ... cgrates_auth("$fU", "$rU"); + + # in compat mode xlog("Call is allowed to run $cgrret seconds\n"); + + # in non-compat mode + xlog("Call is allowed to run $cgrret(MaxUsage) seconds\n"); ... @@ -878,7 +885,7 @@ route { $cgr(SetupTime) = "" + $Ts; $cgr(RequestType) = "*prepaid"; $cgr(Destination) = $rU; - async(cgrates_cmd("SMGenericV1.GetMaxUsage"), auth_reply); + async(cgrates_cmd("SessionSv1.AuthorizeEvent"), auth_reply); } route [auth_reply] From 51c91da1909a72045515dbab6b41b2de24db9477 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Wed, 21 Mar 2018 18:55:56 +0200 Subject: [PATCH 05/10] cgrates: MaxUsage is a float now --- modules/cgrates/cgrates_auth.c | 2 +- modules/cgrates/cgrates_common.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/cgrates/cgrates_auth.c b/modules/cgrates/cgrates_auth.c index a3a6eaa6a7..3e0aee03e9 100644 --- a/modules/cgrates/cgrates_auth.c +++ b/modules/cgrates/cgrates_auth.c @@ -72,7 +72,7 @@ static json_object *cgr_get_auth_msg(struct sip_msg *msg, str *acc, str *dst, st { struct cgr_session *s; struct cgr_msg *cmsg = NULL; - static str cmd_ng = str_init("SessionSv1.AuthorizeEvent"); + static str cmd_ng = str_init("SessionSv1.AuthorizeEventWithDigest"); static str cmd_compat = str_init("SMGenericV1.GetMaxUsage"); str stime; str *cmd = (cgre_compat_mode ? &cmd_compat: &cmd_ng); diff --git a/modules/cgrates/cgrates_common.c b/modules/cgrates/cgrates_common.c index defbbb7b15..a9b7b5bc3e 100644 --- a/modules/cgrates/cgrates_common.c +++ b/modules/cgrates/cgrates_common.c @@ -215,9 +215,12 @@ int cgrates_set_reply_with_values(json_object *jobj) case json_type_null: continue; case json_type_boolean: + case json_type_double: case json_type_int: if (json_object_get_type(v) == json_type_int) val.n = json_object_get_int(v); + if (json_object_get_type(v) == json_type_double) + val.n = (int)json_object_get_double(v); /* lower precision to int :( */ else if (json_object_get_boolean(v)) val.n = 1; else @@ -229,7 +232,6 @@ int cgrates_set_reply_with_values(json_object *jobj) break; case json_type_string: - case json_type_double: case json_type_object: case json_type_array: val.s.s = (char *)json_object_to_json_string(jobj); From 69b89c3c4091f28242c9938aaa0de16afd29a5ba Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 22 Mar 2018 11:58:22 +0200 Subject: [PATCH 06/10] cgrates: save reply objs for cgrates_cmd() --- modules/cgrates/cgrates_cmd.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/cgrates/cgrates_cmd.c b/modules/cgrates/cgrates_cmd.c index b1da338ceb..b4c6e65be5 100644 --- a/modules/cgrates/cgrates_cmd.c +++ b/modules/cgrates/cgrates_cmd.c @@ -40,6 +40,9 @@ static int cgr_proc_cmd_reply(struct cgr_conn *c, json_object *jobj, return -2; } + if (!cgre_compat_mode) + return cgrates_set_reply_with_values(jobj); + switch (json_object_get_type(jobj)) { case json_type_int: val.n = json_object_get_int(jobj); @@ -78,6 +81,7 @@ static int cgr_proc_cmd_reply(struct cgr_conn *c, json_object *jobj, LM_ERR("cannot set the reply value!\n"); return -1; } + return ret; } From 3c7ac2d99d53639d9cbcfea282ea6d83f658862e Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 22 Mar 2018 13:03:30 +0200 Subject: [PATCH 07/10] cgrates: add support for SessionSv1 accounting --- modules/cgrates/cgrates_acc.c | 178 +++++++++++++++++++++++++------ modules/cgrates/cgrates_common.c | 1 + 2 files changed, 145 insertions(+), 34 deletions(-) diff --git a/modules/cgrates/cgrates_acc.c b/modules/cgrates/cgrates_acc.c index 179eececc2..576a170d65 100644 --- a/modules/cgrates/cgrates_acc.c +++ b/modules/cgrates/cgrates_acc.c @@ -199,15 +199,36 @@ static int cgr_proc_start_acc_reply(struct cgr_conn *c, json_object *jobj, struct dlg_cell *dlg = (struct dlg_cell *)p; /* we cannot set in the context, because we don't have access to it */ - if (error) + if (error) /* XXX: should we set a terminate reason? */ return -1; - if (json_object_get_type(jobj) != json_type_int) { - LM_ERR("CGRateS returned a non-int type in InitiateSession reply: %d %s\n", - json_object_get_type(jobj), json_object_to_json_string(jobj)); - return -4; + if (!cgre_compat_mode) { + if (json_object_get_type(jobj) != json_type_object) { + LM_ERR("CGRateS did not return an object in InitiateSession reply: %d %s\n", + json_object_get_type(jobj), json_object_to_json_string(jobj)); + return -4; + } + /* we are only interested in the MaxUsage token */ + jobj = json_object_object_get(jobj, "MaxUsage"); + if (!jobj) { + LM_ERR("CGRateS did not return an MaxUsage in InitiateSession reply: %d %s\n", + json_object_get_type(jobj), json_object_to_json_string(jobj)); + return -4; + } + if (json_object_get_type(jobj) != json_type_int) { + LM_ERR("CGRateS returned a non-int type for MaxUsage InitiateSession reply: %d %s\n", + json_object_get_type(jobj), json_object_to_json_string(jobj)); + return -4; + } + val.n = json_object_get_int64(jobj) / 1000000000; + } else { + if (json_object_get_type(jobj) != json_type_int) { + LM_ERR("CGRateS returned a non-int type for InitiateSession reply: %d %s\n", + json_object_get_type(jobj), json_object_to_json_string(jobj)); + return -4; + } + val.n = json_object_get_int(jobj); } - val.n = json_object_get_int(jobj); /* -1: always allowed (postpaid) * 0: not allowed to call * *: allowed @@ -276,21 +297,6 @@ static inline int has_totag(struct sip_msg *msg) } -static inline int ALLOW_UNUSED cgr_help_set_str(str **dst, str src) -{ - if (*dst) - shm_free(*dst); - *dst = shm_malloc(sizeof(str) + src.len); - if (!(*dst)) { - LM_ERR("out of shm memory\n"); - return -1; - } - (*dst)->s = ((char *)(*dst)) + sizeof(str); - (*dst)->len = src.len; - memcpy((*dst)->s, src.s, src.len); - return 0; -} - static inline str *cgr_get_sess_callid(struct sip_msg *msg, struct cgr_session *s, str *msg_cid) { @@ -321,16 +327,26 @@ static json_object *cgr_get_start_acc_msg(struct sip_msg *msg, { struct cgr_msg *cmsg; struct cgr_acc_sess *si = (struct cgr_acc_sess *)s->acc_info; - static str cmd = str_init("SMGenericV1.InitiateSession"); + static str cmd_compat = str_init("SMGenericV1.InitiateSession"); + static str cmd_ng = str_init("SessionSv1.InitiateSession"); str stime; str *callid; + str *cmd = (cgre_compat_mode ? &cmd_compat: &cmd_ng); - cmsg = cgr_get_generic_msg(&cmd, s); + cmsg = cgr_get_generic_msg(cmd, s); if (!cmsg) { LM_ERR("cannot create generic cgrates message!\n"); return NULL; } + /* ask to init the call */ + if (!cgre_compat_mode && + ((s && !cgr_get_const_kv(&s->req_kvs, "InitSession")) || !s) && + cgr_obj_push_bool(cmsg->opts, "InitSession", 1) < 0) { + LM_ERR("cannot push InitSession to request opts!\n"); + goto error; + } + /* OriginID */ /* if origin was not added from script, add it now */ if (ctx && !cgr_get_const_kv(&s->event_kvs, "OriginID")) { @@ -401,7 +417,9 @@ static json_object *cgr_get_stop_acc_msg(struct sip_msg *msg, struct cgr_msg *cmsg = NULL; char int2str_buf[INT2STR_MAX_LEN + 1]; time_t now = time(NULL); - static str cmd = str_init("SMGenericV1.TerminateSession"); + static str cmd_compat = str_init("SMGenericV1.TerminateSession"); + static str cmd_ng = str_init("SessionSv1.TerminateSession"); + str *cmd = (cgre_compat_mode ? &cmd_compat: &cmd_ng); str *callid; str tmp; @@ -418,12 +436,20 @@ static json_object *cgr_get_stop_acc_msg(struct sip_msg *msg, return NULL; } - cmsg = cgr_get_generic_msg(&cmd, s); + cmsg = cgr_get_generic_msg(cmd, s); if (!cmsg) { LM_ERR("cannot create generic cgrates message!\n"); return NULL; } + /* ask to terminate the call */ + if (!cgre_compat_mode && + ((s && !cgr_get_const_kv(&s->req_kvs, "TerminateSession")) || !s) && + cgr_obj_push_bool(cmsg->opts, "TerminateSession", 1) < 0) { + LM_ERR("cannot push TerminateSession to request opts!\n"); + goto error; + } + /* OriginID */ /* if origin was not added from script, add it now */ if (ctx && !cgr_get_const_kv(&s->event_kvs, "OriginID")) { @@ -479,11 +505,13 @@ static json_object *cgr_get_cdr_acc_msg(struct sip_msg *msg, str tmp; struct cgr_msg *cmsg = NULL; char int2str_buf[INT2STR_MAX_LEN + 1]; - static str cmd = str_init("SMGenericV1.ProcessCDR"); + static str cmd_compat = str_init("SMGenericV1.ProcessCDR"); + static str cmd_ng = str_init("SessionSv1.ProcessCDR"); struct cgr_acc_sess *si = (struct cgr_acc_sess *)s->acc_info; struct dlg_cell *dlg = cgr_dlgb.get_dlg(); + str *cmd = (cgre_compat_mode ? &cmd_compat: &cmd_ng); - cmsg = cgr_get_generic_msg(&cmd, s); + cmsg = cgr_get_generic_msg(cmd, s); if (!cmsg) { LM_ERR("cannot create generic cgrates message!\n"); return NULL; @@ -559,7 +587,10 @@ static void cgr_cdr(struct sip_msg *msg, struct cgr_acc_ctx *ctx, static void cgr_dlg_onshutdown(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) { - int *sessions_kvs = NULL, *tmp; + struct _cgr_sess_no { + unsigned keys; + unsigned options; + } *sessions_kvs = NULL, *tmp; struct cgr_acc_ctx *ctx; struct cgr_session *s; struct list_head *ls; @@ -577,14 +608,15 @@ static void cgr_dlg_onshutdown(struct dlg_cell *dlg, int type, list_for_each(l, ctx->sessions) { sessions_no++; s = list_entry(l, struct cgr_session, list); - tmp = pkg_realloc(sessions_kvs, (sessions_no) * sizeof(int)); + tmp = pkg_realloc(sessions_kvs, (sessions_no) * sizeof(struct _cgr_sess_no)); if (!tmp) { if (sessions_kvs) pkg_free(sessions_kvs); return; } sessions_kvs = tmp; - sessions_kvs[sessions_no-1] = 0; + sessions_kvs[sessions_no-1].keys = 0; + sessions_kvs[sessions_no-1].options = 0; /* if it was not yet engaged, skip it */ if (!s->acc_info) { @@ -599,11 +631,23 @@ static void cgr_dlg_onshutdown(struct dlg_cell *dlg, int type, sizeof(unsigned) + s->acc_info->dst.len /* dst */ + sizeof(branch_bm_t) /* branch_mask */ + sizeof(unsigned) /* flags */ + - sizeof(unsigned) /* number of keys */; + sizeof(unsigned) /* number of keys */ + + sizeof(unsigned) /* number of opts */; /* count the keys now */ list_for_each(ls, &s->event_kvs) { - sessions_kvs[sessions_no-1]++; + sessions_kvs[sessions_no-1].keys++; + kv = list_entry(ls, struct cgr_kv, list); + buf.len += sizeof(unsigned) + kv->key.len + sizeof(unsigned char); + if (kv->flags & CGR_KVF_TYPE_INT) + buf.len += sizeof(int); + else if (kv->flags & CGR_KVF_TYPE_STR) + buf.len += sizeof(unsigned) + kv->value.s.len; + } + + /* count the request opts now */ + list_for_each(ls, &s->req_kvs) { + sessions_kvs[sessions_no-1].options++; kv = list_entry(ls, struct cgr_kv, list); buf.len += sizeof(unsigned) + kv->key.len + sizeof(unsigned char); if (kv->flags & CGR_KVF_TYPE_INT) @@ -672,8 +716,8 @@ static void cgr_dlg_onshutdown(struct dlg_cell *dlg, int type, memcpy(p, &s->acc_info->flags, sizeof(unsigned)); p += sizeof(unsigned); - /* number of keys */ - memcpy(p, &sessions_kvs[i], sizeof(unsigned)); + /* number of event keys */ + memcpy(p, &sessions_kvs[i].keys, sizeof(unsigned)); p += sizeof(unsigned); list_for_each(ls, &s->event_kvs) { @@ -702,6 +746,37 @@ static void cgr_dlg_onshutdown(struct dlg_cell *dlg, int type, p += kv->value.s.len; } } + + /* number of options keys */ + memcpy(p, &sessions_kvs[i].options, sizeof(unsigned)); + p += sizeof(unsigned); + + list_for_each(ls, &s->req_kvs) { + /* if does not have acc_info, it does not have start_time */ + kv = list_entry(ls, struct cgr_kv, list); + + /* kv->key */ + memcpy(p, &kv->key.len, sizeof(unsigned)); + p += sizeof(unsigned); + memcpy(p, kv->key.s, kv->key.len); + p += kv->key.len; + LM_DBG("storing opt key %d [%.*s]\n", kv->key.len,kv->key.len,kv->key.s); + + /* kv->flags */ + memcpy(p, &kv->flags, sizeof(unsigned char)); + p += sizeof(unsigned char); + + /* kv->value */ + if (kv->flags & CGR_KVF_TYPE_INT) { + memcpy(p, &kv->value.n, sizeof(int)); + p += sizeof(int); + } else if (kv->flags & CGR_KVF_TYPE_STR) { + memcpy(p, &kv->value.s.len, sizeof(unsigned)); + p += sizeof(unsigned); + memcpy(p, kv->value.s.s, kv->value.s.len); + p += kv->value.s.len; + } + } } } @@ -1089,6 +1164,41 @@ void cgr_loaded_callback(struct dlg_cell *dlg, int type, /* all good - link the new value */ list_add(&kv->list, &s->event_kvs); } + CGR_CTX_COPY(&kvs_no, sizeof(unsigned), "req kvs no"); + while (kvs_no > 0) { + kvs_no--; + //LM_DBG("p=%p end=%p\n", p, end); + CGR_CTX_COPY(&kvs.len, sizeof(unsigned), "key.len"); + /* do the key manually because it's not worth doing a copy */ + if (p + kvs.len <= end) { + kvs.s = p; + p += kvs.len; + } else { + LM_ERR("invalid ctx stored buffer: no more length for key.str\n"); + goto internal_error; + } + LM_DBG("adding key %d [%.*s]\n", kvs.len, kvs.len, kvs.s); + kv = cgr_new_kv(kvs); + if (!kv) { + LM_ERR("cannot allocate a new kv\n"); + goto internal_error; + } + CGR_CTX_COPY(&kv->flags, sizeof(unsigned char), "key.flags"); + if (kv->flags & CGR_KVF_TYPE_INT) + CGR_CTX_COPY(&kv->value.n, sizeof(int), "key.value.int"); + else if (kv->flags & CGR_KVF_TYPE_STR) { + CGR_CTX_COPY(&kv->value.s.len, sizeof(unsigned), "key.value.str.len"); + kv->value.s.s = shm_malloc(kv->value.s.len); + if (!kv->value.s.s) { + LM_ERR("out of shm mem!\n"); + cgr_free_kv(kv); + goto internal_error; + } + CGR_CTX_COPY(kv->value.s.s, kv->value.s.len, "key.value.str.s"); + } + /* all good - link the new value */ + list_add(&kv->list, &s->req_kvs); + } } store: if (p != end) diff --git a/modules/cgrates/cgrates_common.c b/modules/cgrates/cgrates_common.c index a9b7b5bc3e..5240d15777 100644 --- a/modules/cgrates/cgrates_common.c +++ b/modules/cgrates/cgrates_common.c @@ -911,6 +911,7 @@ int cgrates_process(json_object *jobj, /* check to see if there is an id */ json_object_object_get_ex(jobj, "id", &id); + json_object_get(id); cgrates_process_req(c, id, method, jresult); } return 0; From 654418d9e549f4a0fbf8c7d220074b999eb63b62 Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 22 Mar 2018 13:06:52 +0200 Subject: [PATCH 08/10] cgrates: adjust auth documentation --- modules/cgrates/README | 5 +++-- modules/cgrates/doc/cgrates_admin.xml | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/cgrates/README b/modules/cgrates/README index 7b6f75f889..a36fc57f8a 100644 --- a/modules/cgrates/README +++ b/modules/cgrates/README @@ -623,7 +623,7 @@ route { $cgr(SetupTime) = "" + $Ts; $cgr(RequestType) = "*prepaid"; $cgr(Destination) = $rU; - async(cgrates_cmd("SessionSv1.AuthorizeEvent"), auth_reply); + async(cgrates_cmd("SMGenericV1.GetMaxUsage"), auth_reply); } route [auth_reply] @@ -652,7 +652,8 @@ eply); route [auth_reply] { if ($rc < 0) { - xlog("Call not authorized: code=$cgrret!\n"); + xlog("Call not authorized: MaxUsage=$cgrret(MaxUsage)!\n +"); send_reply("403", "Forbidden"); exit; } diff --git a/modules/cgrates/doc/cgrates_admin.xml b/modules/cgrates/doc/cgrates_admin.xml index bfec78a102..27aee4a185 100644 --- a/modules/cgrates/doc/cgrates_admin.xml +++ b/modules/cgrates/doc/cgrates_admin.xml @@ -885,7 +885,7 @@ route { $cgr(SetupTime) = "" + $Ts; $cgr(RequestType) = "*prepaid"; $cgr(Destination) = $rU; - async(cgrates_cmd("SessionSv1.AuthorizeEvent"), auth_reply); + async(cgrates_cmd("SMGenericV1.GetMaxUsage"), auth_reply); } route [auth_reply] @@ -916,7 +916,7 @@ route { route [auth_reply] { if ($rc < 0) { - xlog("Call not authorized: code=$cgrret!\n"); + xlog("Call not authorized: MaxUsage=$cgrret(MaxUsage)!\n"); send_reply("403", "Forbidden"); exit; } From 7f7ca1f8c674dc52978d400bcde73baddf6ba28c Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 22 Mar 2018 14:36:53 +0200 Subject: [PATCH 09/10] cgrates: allow seting NULL to variables --- modules/cgrates/cgrates.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/cgrates/cgrates.c b/modules/cgrates/cgrates.c index 01f0bdafb8..71100dfec4 100644 --- a/modules/cgrates/cgrates.c +++ b/modules/cgrates/cgrates.c @@ -393,7 +393,7 @@ static int pv_set_cgr(struct sip_msg *msg, pv_param_t *param, struct cgr_kv *kv; int dup; - if (!param || !val) { + if (!param) { LM_ERR("invalid parameter or value to set\n"); return -1; } @@ -435,19 +435,21 @@ static int pv_set_cgr(struct sip_msg *msg, pv_param_t *param, if (kv) { /* replace the old value */ cgr_free_kv_val(kv); - if (val->flags & PV_VAL_NULL && op == COLONEQ_T) { + if ((!val || val->flags & PV_VAL_NULL) && op == COLONEQ_T) { /* destroy the value */ cgr_free_kv(kv); return 0; } - } else { + } else if (val) { kv = cgr_new_real_kv(name_val.rs.s, name_val.rs.len, dup); if (!kv) { LM_ERR("cannot allocate new key-value\n"); return -1; } list_add(&kv->list, kvs); - } + } else + return 0; /* initialised with NULL */ + if (val->flags & PV_VAL_NULL) { kv->flags |= CGR_KVF_TYPE_NULL; } else if (val->flags & PV_VAL_INT) { From 033773e728646c54ad8e0b7c21c0a9975c437abf Mon Sep 17 00:00:00 2001 From: Razvan Crainea Date: Thu, 22 Mar 2018 15:11:15 +0200 Subject: [PATCH 10/10] cgrates: allow SessionSv1.DisconnectSession rpc --- modules/cgrates/cgrates_common.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/cgrates/cgrates_common.c b/modules/cgrates/cgrates_common.c index 5240d15777..b616be925a 100644 --- a/modules/cgrates/cgrates_common.c +++ b/modules/cgrates/cgrates_common.c @@ -782,7 +782,8 @@ static inline int cgrates_process_req(struct cgr_conn *c, json_object *id, LM_INFO("Received new request method=%s param=%p\n", method, param); - if (strcmp(method, "SMGClientV1.DisconnectSession") == 0) { + if (strcmp(method, "SMGClientV1.DisconnectSession") == 0 || + strcmp(method, "SessionSv1.DisconnectSession") == 0) { ret = cgr_acc_terminate(param, &jret); } else { LM_ERR("cannot handle method %s\n", method);