From a61748472219551f27abf45edefd24ed67eb9df7 Mon Sep 17 00:00:00 2001 From: DanB Date: Wed, 7 Feb 2018 09:49:03 +0100 Subject: [PATCH] Automatic usage in case of Suppliers --- agents/librad.go | 6 +- data/daemontools/run | 4 - .../conf/autoload_configs/cdr_csv.conf.xml | 23 - .../conf/autoload_configs/json_cdr.conf.xml | 51 --- .../freeswitch/conf/dialplan/cgr_dialplan.xml | 71 --- .../conf/directory/default/1000.xml | 28 -- .../etc/kamailio/kamailio-cgrates.cfg | 149 ------- data/kamailio/etc/kamailio/kamailio.cfg | 408 ------------------ engine/suppliers.go | 10 +- utils/rsrfield_test.go | 1 + 10 files changed, 11 insertions(+), 740 deletions(-) delete mode 100755 data/daemontools/run delete mode 100644 data/freeswitch/conf/autoload_configs/cdr_csv.conf.xml delete mode 100644 data/freeswitch/conf/autoload_configs/json_cdr.conf.xml delete mode 100644 data/freeswitch/conf/dialplan/cgr_dialplan.xml delete mode 100644 data/freeswitch/conf/directory/default/1000.xml delete mode 100644 data/kamailio/etc/kamailio/kamailio-cgrates.cfg delete mode 100644 data/kamailio/etc/kamailio/kamailio.cfg diff --git a/agents/librad.go b/agents/librad.go index 58d2dd82c..d21317631 100644 --- a/agents/librad.go +++ b/agents/librad.go @@ -132,14 +132,15 @@ func radPassesFieldFilter(pkt *radigo.Packet, processorVars processorVars, } // radComposedFieldValue extracts the field value out of RADIUS packet +// procVars have priority over packet variables func radComposedFieldValue(pkt *radigo.Packet, - processorVars processorVars, outTpl utils.RSRFields) (outVal string) { + procVars processorVars, outTpl utils.RSRFields) (outVal string) { for _, rsrTpl := range outTpl { if rsrTpl.IsStatic() { outVal += rsrTpl.ParseValue("") continue } - if val, err := processorVars.valAsString(rsrTpl.Id); err != nil { + if val, err := procVars.valAsString(rsrTpl.Id); err != nil { if err.Error() != "not found" { utils.Logger.Warning( fmt.Sprintf("<%s> %s", @@ -148,6 +149,7 @@ func radComposedFieldValue(pkt *radigo.Packet, } } else { outVal += rsrTpl.ParseValue(val) + continue } for _, avp := range pkt.AttributesWithName( attrVendorFromPath(rsrTpl.Id)) { diff --git a/data/daemontools/run b/data/daemontools/run deleted file mode 100755 index a8f8f6639..000000000 --- a/data/daemontools/run +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -echo Firing up CGRateS engine -exec setuidgid cgrates /usr/bin/cgr-engine -config /etc/cgrates/cgrates.cfg - diff --git a/data/freeswitch/conf/autoload_configs/cdr_csv.conf.xml b/data/freeswitch/conf/autoload_configs/cdr_csv.conf.xml deleted file mode 100644 index 8d796c68f..000000000 --- a/data/freeswitch/conf/autoload_configs/cdr_csv.conf.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/freeswitch/conf/autoload_configs/json_cdr.conf.xml b/data/freeswitch/conf/autoload_configs/json_cdr.conf.xml deleted file mode 100644 index 8bf79c2f9..000000000 --- a/data/freeswitch/conf/autoload_configs/json_cdr.conf.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/freeswitch/conf/dialplan/cgr_dialplan.xml b/data/freeswitch/conf/dialplan/cgr_dialplan.xml deleted file mode 100644 index c821ecb6f..000000000 --- a/data/freeswitch/conf/dialplan/cgr_dialplan.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/freeswitch/conf/directory/default/1000.xml b/data/freeswitch/conf/directory/default/1000.xml deleted file mode 100644 index 5a37a0133..000000000 --- a/data/freeswitch/conf/directory/default/1000.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/kamailio/etc/kamailio/kamailio-cgrates.cfg b/data/kamailio/etc/kamailio/kamailio-cgrates.cfg deleted file mode 100644 index 7db28931b..000000000 --- a/data/kamailio/etc/kamailio/kamailio-cgrates.cfg +++ /dev/null @@ -1,149 +0,0 @@ -# Kamailio-CGRateS related route blocks - -# Called on new connection over evapi, should normally be the case of CGRateS engine -event_route[evapi:connection-new] { - $sht(cgrconn=>cgr) = $evapi(srcaddr) + ":" + $evapi(srcport); # Detect presence of at least one connection -} - -# Called when the connection with CGRateS closes -event_route[evapi:connection-closed] { - $var(connClosed) = $evapi(srcaddr) + ":" + $evapi(srcport); - if $sht(cgrconn=>cgr) == $var(connClosed) { - $sht(cgrconn=>cgr) = $null; - } -} - -# Message received from CGRateS, dispatch it to own route -event_route[evapi:message-received] { - json_get_field("$evapi(msg)", "Event", "$var(Event)"); - route($(var(Event){s.rm,"})); # String characters are kept by json_get_field, remove them here -} - -# Called by Kamailio on new dialog -event_route[dialog:start] { - route(CGR_CALL_START); -} - -# Called by Kamailio on dialog end -event_route[dialog:end] { - route(CGR_CALL_END); -} - -# Called by Kamailio on local disconnect -event_route[tm:local-request] { - route(CGR_CALL_END); -} - - -# Send AUTH_REQUEST to CGRateS -route[CGRATES_AUTH_REQUEST] { - # Auth INVITEs with CGRateS - if $sht(cgrconn=>cgr) == $null { - sl_send_reply("503","Charging controller unreachable"); - exit; - } - evapi_async_relay("{\"event\":\"CGR_AUTH_REQUEST\", - \"tr_index\":\"$T(id_index)\", - \"tr_label\":\"$T(id_label)\", - \"cgr_reqtype\":\"$dlg_var(cgrReqType)\", - \"cgr_tenant\":\"$dlg_var(cgrTenant)\", - \"cgr_account\":\"$dlg_var(cgrAccount)\", - \"cgr_destination\":\"$dlg_var(cgrDestination)\", - \"cgr_setuptime\":\"$TS\", - \"cgr_computelcr\":\"true\"}"); -} - -# Process AUTH_REPLY from CGRateS -route[CGR_AUTH_REPLY] { - json_get_field("$evapi(msg)", "TransactionIndex", "$var(TransactionIndex)"); - json_get_field("$evapi(msg)", "TransactionLabel", "$var(TransactionLabel)"); - json_get_field("$evapi(msg)", "MaxSessionTime", "$var(MaxSessionTime)"); - json_get_field("$evapi(msg)", "Suppliers", "$var(Suppliers)"); - json_get_field("$evapi(msg)", "Error", "$var(Error)"); - $var(id_index) = $(var(TransactionIndex){s.int}); - $var(id_label) = $(var(TransactionLabel){s.int}); - $var(CgrMaxSessionTime) = $(var(MaxSessionTime){s.int}); - $var(CgrSuppliers) = $(var(Suppliers){s.rm,"}); - $var(CgrError) = $(var(Error){s.rm,"}); - t_continue("$var(id_index)", "$var(id_label)", "CGRATES_AUTH_REPLY"); # Unpark the transaction -} - -# Send AUTH_REQUEST to CGRateS -route[CGRATES_LCR_REQUEST] { - # Auth INVITEs with CGRateS - if $sht(cgrconn=>cgr) == $null { - sl_send_reply("503","Charging controller unreachable"); - exit; - } - evapi_async_relay("{\"event\":\"CGR_LCR_REQUEST\", - \"tr_index\":\"$T(id_index)\", - \"tr_label\":\"$T(id_label)\", - \"cgr_tenant\":\"$dlg_var(cgrTenant)\", - \"cgr_account\":\"$dlg_var(cgrAccount)\", - \"cgr_destination\":\"$dlg_var(cgrDestination)\", - \"cgr_setuptime\":\"$TS\"}"); -} - -# Process LCR_REPLY from CGRateS -route[CGR_LCR_REPLY] { - json_get_field("$evapi(msg)", "TransactionIndex", "$var(TransactionIndex)"); - json_get_field("$evapi(msg)", "TransactionLabel", "$var(TransactionLabel)"); - json_get_field("$evapi(msg)", "MaxSessionTime", "$var(MaxSessionTime)"); - json_get_field("$evapi(msg)", "Suppliers", "$var(Suppliers)"); - json_get_field("$evapi(msg)", "Error", "$var(Error)"); - $var(id_index) = $(var(TransactionIndex){s.int}); - $var(id_label) = $(var(TransactionLabel){s.int}); - $var(CgrMaxSessionTime) = $(var(MaxSessionTime){s.int}); - $var(CgrSuppliers) = $(var(Suppliers){s.rm,"}); - $var(CgrError) = $(var(Error){s.rm,"}); - t_continue("$var(id_index)", "$var(id_label)", "CGRATES_AUTH_REPLY"); # Unpark the transaction -} - -# CGRateS request for session disconnect -route[CGR_SESSION_DISCONNECT] { - json_get_field("$evapi(msg)", "HashEntry", "$var(HashEntry)"); - json_get_field("$evapi(msg)", "HashId", "$var(HashId)"); - json_get_field("$evapi(msg)", "Reason", "$var(Reason)"); - jsonrpc_exec('{"jsonrpc":"2.0","id":1, "method":"dlg.end_dlg","params":[$(var(HashEntry){s.rm,"}),$(var(HashId){s.rm,"})]}'); - #$jsonrpl($var(reply)); -} - -# Inform CGRateS about CALL_START (start prepaid sessions loops) -route[CGR_CALL_START] { - if $sht(cgrconn=>cgr) == $null { - xlog("Charging controller unreachable"); - exit; - } - evapi_relay("{\"event\":\"CGR_CALL_START\", - \"callid\":\"$dlg(callid)\", - \"from_tag\":\"$dlg(from_tag)\", - \"h_entry\":\"$dlg(h_entry)\", - \"h_id\":\"$dlg(h_id)\", - \"cgr_reqtype\":\"$dlg_var(cgrReqType)\", - \"cgr_tenant\":\"$dlg_var(cgrTenant)\", - \"cgr_account\":\"$dlg_var(cgrAccount)\", - \"cgr_destination\":\"$dlg_var(cgrDestination)\", - \"cgr_answertime\":\"$TS\", - \"cgr_supplier\":\"$dlg_var(cgrSupplier)\"}"); -} - -# Inform CGRateS about CALL_END (stop debit loops, perform accounting if desired in this way) -route[CGR_CALL_END] { - if $sht(cgrconn=>cgr) == $null { - xlog("Charging controller unreachable"); - exit; - } - $var(callDur) = $TS - $dlg(start_ts); - evapi_relay("{\"event\":\"CGR_CALL_END\", - \"callid\":\"$dlg(callid)\", - \"from_tag\":\"$dlg(from_tag)\", - \"cgr_reqtype\":\"$dlg_var(cgrReqType)\", - \"cgr_tenant\":\"$dlg_var(cgrTenant)\", - \"cgr_account\":\"$dlg_var(cgrAccount)\", - \"cgr_destination\":\"$dlg_var(cgrDestination)\", - \"cgr_answertime\":\"$dlg(start_ts)\", - \"cgr_duration\":\"$var(callDur)\", - \"cgr_supplier\":\"$dlg_var(cgrSupplier)\", - \"cgr_disconnectcause\":\"$T_reply_code\"}"); -} - diff --git a/data/kamailio/etc/kamailio/kamailio.cfg b/data/kamailio/etc/kamailio/kamailio.cfg deleted file mode 100644 index d751bcebe..000000000 --- a/data/kamailio/etc/kamailio/kamailio.cfg +++ /dev/null @@ -1,408 +0,0 @@ -#!KAMAILIO - -####### Defined Values ######### - -#!define FLT_DIALOG 4 -#!define FLT_NATS 5 -#!define FLB_NATB 6 -#!define FLB_NATSIPPING 7 - -####### Global Parameters ######### - -debug=2 -log_stderror=no - -memdbg=5 -memlog=5 -log_facility=LOG_LOCAL0 -fork=yes -children=4 -tcp_connection_lifetime=3605 -use_dns_cache=no -dns_try_ipv6=no -dns_retr_time=1 -dns_retr_no=1 -dns_servers_no=1 -dns_use_search_list=no - -####### Modules Section ######## - -mpath="/usr/lib/x86_64-linux-gnu/kamailio/modules/" - -loadmodule "kex.so" -loadmodule "corex.so" -loadmodule "tm.so" -loadmodule "tmx.so" -loadmodule "sl.so" -loadmodule "rr.so" -loadmodule "pv.so" -loadmodule "maxfwd.so" -loadmodule "usrloc.so" -loadmodule "registrar.so" -loadmodule "textops.so" -loadmodule "siputils.so" -loadmodule "xlog.so" -loadmodule "sanity.so" -loadmodule "nathelper.so" -loadmodule "htable.so" -loadmodule "auth.so" -loadmodule "evapi.so" -loadmodule "json.so" -loadmodule "dialog.so" -loadmodule "xhttp.so" -loadmodule "jsonrpc-s.so" - - - -# ----------------- setting module-specific parameters --------------- - - -# ----- tm params ----- -modparam("tm", "failure_reply_mode", 3) -modparam("tm", "fr_timer", 30000) -modparam("tm", "fr_inv_timer", 120000) - -# ----- rr params ----- -modparam("rr", "enable_full_lr", 0) -modparam("rr", "append_fromtag", 0) - -# ----- registrar params ----- -modparam("registrar", "method_filtering", 1) -modparam("registrar", "max_expires", 3600) - -# ----- dialog params ----- -modparam("dialog", "dlg_flag", FLT_DIALOG) -modparam("dialog", "send_bye", 1) -modparam("dialog", "timeout_noreset", 1) - -# ----- nathelper params ----- -modparam("nathelper", "natping_interval", 30) -modparam("nathelper", "ping_nated_only", 1) -modparam("nathelper", "sipping_bflag", FLB_NATSIPPING) -modparam("nathelper", "sipping_from", "sip:pinger@kamailio.org") - -# params needed for NAT traversal in other modules -modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)") -modparam("usrloc", "nat_bflag", FLB_NATB) - -# ----- htable params ----- -modparam("htable", "htable", "users=>size=8;") -modparam("htable", "htable", "cgrconn=>size=1;") - -####### Routing Logic ######## - -include_file "kamailio-cgrates.cfg" - -event_route[htable:mod-init] { - $sht(users=>1001) = "CGRateS.org"; - $sht(users=>1002) = "CGRateS.org"; - $sht(users=>1003) = "CGRateS.org"; - $sht(users=>1004) = "CGRateS.org"; - $sht(users=>1005) = "CGRateS.org"; - $sht(users=>1006) = "CGRateS.org"; - $sht(users=>1007) = "CGRateS.org"; -} - - -# Main SIP request routing logic -request_route { - - # per request initial checks - route(REQINIT); - - # NAT detection - route(NATDETECT); - - # CANCEL processing - if (is_method("CANCEL")) { - if (t_check_trans()) { - route(RELAY); - } - exit; - } - - # handle requests within SIP dialogs - route(WITHINDLG); - - ### only initial requests (no To tag) - - # handle retransmissions - if(t_precheck_trans()) { - t_check_trans(); - exit; - } - t_check_trans(); - - # authentication - route(AUTH); - - # record routing for dialog forming requests (in case they are routed) - # - remove preloaded route headers - remove_hf("Route"); - if (is_method("INVITE|SUBSCRIBE")) - record_route(); - - # Not handling requests towards external domains - if uri != myself { - sl_send_reply("604", "Only local destinations accepted"); - exit; - } - - ### requests for my local domains - - # handle registrations - route(REGISTRAR); - - if ($rU==$null) { - # request with no Username in RURI - sl_send_reply("484","Address Incomplete"); - exit; - } - - # user location service - route(LOCATION); - if !is_method("INVITE") { - route(RELAY); - } - dlg_manage(); - switch ($fU) { - case 1001: - case 1006: - case 1007: - $dlg_var(cgrReqType) = "*prepaid"; - break; - case 1002: - $dlg_var(cgrReqType) = "*postpaid"; - break; - case 1003: - $dlg_var(cgrReqType) = "*pseudoprepaid"; - break; - default: - $dlg_var(cgrReqType) = "*rated"; - } - $dlg_var(cgrTenant) = "cgrates.org"; - $dlg_var(cgrAccount) = $fU; - $dlg_var(cgrDestination) = $rU; - route(CGRATES_AUTH_REQUEST); # Will be answered in CGRATES_AUTH_REPLY - #route(CGRATES_LCR_REQUEST); - exit; -} - -# Here will land requests after processing them with CGRateS. Call RELAY or other routes following this route -route[CGRATES_AUTH_REPLY] { - if $var(CgrError) != "" { - xlog("CGR_AUTH_ERROR: $var(CgrError)"); - sl_send_reply("503","CGR_ERROR"); - exit; - } - if $var(CgrMaxSessionTime) != -1 { - if $var(CgrMaxSessionTime) == 0 { // Not enough balance, do not allow the call to go through - sl_send_reply("403","Insufficient credit"); - exit; - } else if !dlg_set_timeout("$var(CgrMaxSessionTime)") { - sl_send_reply("503","CGR_MAX_SESSION_TIME_ERROR"); - exit; - } - } - if $var(CgrSuppliers) != "" { # Enforce the supplier variable to the first one received from CGRateS, more for testing purposes - $dlg_var(cgrSupplier) = $(var(CgrSuppliers){s.select,0,,}); - } - route(RELAY); -} - -# Wrapper for relaying requests -route[RELAY] { - # enable additional event routes for forwarded requests - # - serial forking, RTP relaying handling, a.s.o. - if (is_method("INVITE|BYE|SUBSCRIBE|UPDATE")) { - if(!t_is_set("branch_route")) t_on_branch("MANAGE_BRANCH"); - } - if (is_method("INVITE|SUBSCRIBE|UPDATE")) { - if(!t_is_set("onreply_route")) t_on_reply("MANAGE_REPLY"); - } - if (is_method("INVITE")) { - if(!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE"); - } - - if (!t_relay()) { - sl_reply_error(); - } - exit; -} - -# Per SIP request initial checks -route[REQINIT] { - if (!mf_process_maxfwd_header("10")) { - sl_send_reply("483","Too Many Hops"); - exit; - } - - if(is_method("OPTIONS") && uri==myself && $rU==$null) { - sl_send_reply("200","Keepalive"); - exit; - } - - if(!sanity_check("1511", "7")) { - xlog("Malformed SIP message from $si:$sp\n"); - exit; - } -} - -# Handle requests within SIP dialogs -route[WITHINDLG] { - if (!has_totag()) return; - - # sequential request withing a dialog should - # take the path determined by record-routing - if (loose_route()) { - route(DLGURI); - if ( is_method("ACK") ) { - # ACK is forwarded statelessy - route(NATMANAGE); - } - else if ( is_method("NOTIFY") ) { - # Add Record-Route for in-dialog NOTIFY as per RFC 6665. - record_route(); - } - route(RELAY); - exit; - } - if ( is_method("ACK") ) { - if ( t_check_trans() ) { - # no loose-route, but stateful ACK; - # must be an ACK after a 487 - # or e.g. 404 from upstream server - route(RELAY); - exit; - } else { - # ACK without matching transaction ... ignore and discard - exit; - } - } - sl_send_reply("404","Not here"); - exit; -} - -# Handle SIP registrations -route[REGISTRAR] { - if (!is_method("REGISTER")) return; - - if(isflagset(FLT_NATS)) { - setbflag(FLB_NATB); - } - if (!save("location")) - sl_reply_error(); - exit; -} - -# User location service -route[LOCATION] { - $avp(oexten) = $rU; - if (!lookup("location")) { - $var(rc) = $rc; - t_newtran(); - switch ($var(rc)) { - case -1: - case -3: - send_reply("404", "Not Found"); - exit; - case -2: - send_reply("405", "Method Not Allowed"); - exit; - } - } -} - -# user uthentication -route[AUTH] { - if (is_method("REGISTER")) { - if ( strempty($au) || !pv_www_authenticate("$td", "$sht(users=>$au)", "0") ) { - www_challenge("$td", "0"); - exit; - } - } else { # All other methods here - if ( strempty($au) || !pv_proxy_authenticate("$td", "$sht(users=>$au)", "0") ) { - proxy_challenge("$td", "0"); - exit; - } - } - consume_credentials(); - return; -} - -# Caller NAT detection -route[NATDETECT] { - force_rport(); - if (nat_uac_test("19")) { - if (is_method("REGISTER")) { - fix_nated_register(); - } else { - if(is_first_hop()) - set_contact_alias(); - } - setflag(FLT_NATS); - } - return; -} - -# RTPProxy control and singaling updates for NAT traversal -route[NATMANAGE] { - if (is_request()) { - if(has_totag()) { - if(check_route_param("nat=yes")) { - setbflag(FLB_NATB); - } - } - } - if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB))) - return; - - if (is_request()) { - if (!has_totag()) { - if(t_is_branch_route()) { - add_rr_param(";nat=yes"); - } - } - } - if (is_reply()) { - if(isbflagset(FLB_NATB)) { - if(is_first_hop()) - set_contact_alias(); - } - } - return; -} - -# URI update for dialog requests -route[DLGURI] { - if(!isdsturiset()) { - handle_ruri_alias(); - } - return; -} - - -# Manage outgoing branches -branch_route[MANAGE_BRANCH] { - route(NATMANAGE); -} - -# Manage incoming replies -onreply_route[MANAGE_REPLY] { - if(status=~"[12][0-9][0-9]") - route(NATMANAGE); -} - -# Manage failure routing cases -failure_route[MANAGE_FAILURE] { - route(NATMANAGE); - - if (t_is_canceled()) { - exit; - } - - if (t_check_status("3[0-9][0-9]")) { - t_reply("404","Not found"); - exit; - } -} diff --git a/engine/suppliers.go b/engine/suppliers.go index fd240ce87..a1cb57009 100644 --- a/engine/suppliers.go +++ b/engine/suppliers.go @@ -160,7 +160,7 @@ func (spS *SupplierService) matchingSupplierProfilesForEvent(ev *utils.CGREvent) func (spS *SupplierService) costForEvent(ev *utils.CGREvent, acntIDs, rpIDs []string) (costData map[string]interface{}, err error) { if err = ev.CheckMandatoryFields([]string{utils.Account, - utils.Destination, utils.SetupTime, utils.Usage}); err != nil { + utils.Destination, utils.SetupTime}); err != nil { return } var acnt, subj, dst string @@ -180,9 +180,11 @@ func (spS *SupplierService) costForEvent(ev *utils.CGREvent, if sTime, err = ev.FieldAsTime(utils.SetupTime, spS.timezone); err != nil { return } - var usage time.Duration - if usage, err = ev.FieldAsDuration(utils.Usage); err != nil { - return + usage := time.Duration(time.Minute) + if _, has := ev.Event[utils.Usage]; has { + if usage, err = ev.FieldAsDuration(utils.Usage); err != nil { + return + } } for _, anctID := range acntIDs { cd := &CallDescriptor{ diff --git a/utils/rsrfield_test.go b/utils/rsrfield_test.go index 414e4ad71..a3516915c 100644 --- a/utils/rsrfield_test.go +++ b/utils/rsrfield_test.go @@ -35,6 +35,7 @@ func TestNewRSRField1(t *testing.T) { } // With filter rulesStr = `~sip_redirected_to:s/sip:\+49(\d+)@/0$1/(086517174963)` + // rulesStr = `~sip_redirected_to:s/sip:\+49(\d+)@/0$1/{*usage_seconds;*round:5:*middle}(086517174963)` filter, _ := NewRSRFilter("086517174963") expRSRField2 := &RSRField{Id: "sip_redirected_to", Rules: rulesStr, filters: []*RSRFilter{filter}, RSRules: []*ReSearchReplace{&ReSearchReplace{SearchRegexp: regexp.MustCompile(`sip:\+49(\d+)@`), ReplaceTemplate: "0$1"}}}