From 40eaa8739c139029fd5dd650a3a7023d477c9b9e Mon Sep 17 00:00:00 2001 From: ionutboangiu Date: Wed, 1 May 2024 16:49:42 +0300 Subject: [PATCH] Add tut entry for opensips+diameter INCOMPLETE - shared cgrates configuration for three different scenarios - every scenario has its own opensips configuration. For more info check README or read comments inside the configuration - added script for converting unix time to suitable Event-Timestamp format - added a dictionary.opensips entry for making Credit-Control diameter requests possible --- data/tutorials/osips_diam/README.md | 30 ++ .../osips_diam/etc/cgrates/accounting.json | 154 ++++++ .../osips_diam/etc/cgrates/cc-postpaid.json | 139 ++++++ .../etc/cgrates/cc-prepaid-auth.json | 139 ++++++ .../etc/cgrates/cc-prepaid-init.json | 126 +++++ .../etc/cgrates/cc-prepaid-terminate.json | 145 ++++++ .../osips_diam/etc/cgrates/cgrates.json | 75 +++ .../etc/opensips/dictionary.opensips | 94 ++++ .../osips_diam/etc/opensips/opensips_acc.cfg | 278 +++++++++++ .../etc/opensips/opensips_ccr_postpaid.cfg | 309 ++++++++++++ .../etc/opensips/opensips_ccr_prepaid.cfg | 440 ++++++++++++++++++ .../osips_diam/etc/opensips/unix2ntp | 10 + .../osips_diam/tp/AccountActions.csv | 2 + data/tutorials/osips_diam/tp/ActionPlans.csv | 2 + data/tutorials/osips_diam/tp/Actions.csv | 2 + data/tutorials/osips_diam/tp/Chargers.csv | 2 + .../osips_diam/tp/DestinationRates.csv | 2 + data/tutorials/osips_diam/tp/Destinations.csv | 2 + data/tutorials/osips_diam/tp/Rates.csv | 3 + data/tutorials/osips_diam/tp/RatingPlans.csv | 2 + .../osips_diam/tp/RatingProfiles.csv | 2 + 21 files changed, 1958 insertions(+) create mode 100644 data/tutorials/osips_diam/README.md create mode 100644 data/tutorials/osips_diam/etc/cgrates/accounting.json create mode 100644 data/tutorials/osips_diam/etc/cgrates/cc-postpaid.json create mode 100644 data/tutorials/osips_diam/etc/cgrates/cc-prepaid-auth.json create mode 100644 data/tutorials/osips_diam/etc/cgrates/cc-prepaid-init.json create mode 100644 data/tutorials/osips_diam/etc/cgrates/cc-prepaid-terminate.json create mode 100644 data/tutorials/osips_diam/etc/cgrates/cgrates.json create mode 100644 data/tutorials/osips_diam/etc/opensips/dictionary.opensips create mode 100644 data/tutorials/osips_diam/etc/opensips/opensips_acc.cfg create mode 100644 data/tutorials/osips_diam/etc/opensips/opensips_ccr_postpaid.cfg create mode 100644 data/tutorials/osips_diam/etc/opensips/opensips_ccr_prepaid.cfg create mode 100755 data/tutorials/osips_diam/etc/opensips/unix2ntp create mode 100644 data/tutorials/osips_diam/tp/AccountActions.csv create mode 100644 data/tutorials/osips_diam/tp/ActionPlans.csv create mode 100644 data/tutorials/osips_diam/tp/Actions.csv create mode 100644 data/tutorials/osips_diam/tp/Chargers.csv create mode 100644 data/tutorials/osips_diam/tp/DestinationRates.csv create mode 100644 data/tutorials/osips_diam/tp/Destinations.csv create mode 100644 data/tutorials/osips_diam/tp/Rates.csv create mode 100644 data/tutorials/osips_diam/tp/RatingPlans.csv create mode 100644 data/tutorials/osips_diam/tp/RatingProfiles.csv diff --git a/data/tutorials/osips_diam/README.md b/data/tutorials/osips_diam/README.md new file mode 100644 index 000000000..af8357f62 --- /dev/null +++ b/data/tutorials/osips_diam/README.md @@ -0,0 +1,30 @@ +# Prepaid Scenario + +1. **INVITE**: 1001 calls 1002. +2. **Send INITIAL_REQUEST CCR**: Before forwarding INVITE, have OpenSIPS send a Diameter Credit-Control INITIAL_REQUEST to authorize the call. +3. **Receive CCA**: Extract CC-Time from the reply's Granted-Service-Unit AVP and (if also authorized) then set the dialog timeout to that value. +4. **ACK**: 1002 answers the call, `dlg_on_answer` handler is triggered. +5. **Send UPDATE_REQUEST CCR**: Send an async Credit-Control request to CGRateS to initiate the session. (see bottom of cfg file to see why, same for the other async calls). No need for further updates as CGRateS will debit based on the configured `debit_interval`. +6. **SEND TERMINATION_REQUEST**: Wait for hangup/timeout. The `handle_hangup/handle_auth` handler will be triggered and send a Diameter Credit-Control TERMINATION_REQUEST. Both of those are almost identical, only differing AVP being Terminate-Cause (6/DIAMETER_AUTH_EXPIRED for timeout AND 1/DIAMETER_LOGOUT for hangup). This will terminate the session as well as process the CDR. + +# Postpaid Scenario + +The Postpaid scenario is the same as the above except: +- We only send an **EVENT_REQUEST** on `dlg_on_timeout/dlg_on_hangup` handlers to send a ProcessCDR request to CGRateS. +- Inside `dlg_on_answer` the answer time is recorded to calculate usage at the end. + +# Accounting Scenario + +Attempts to use the `do_accounting` method (part of the OpenSIPS accounting module) to send Accounting-Start and Accounting-Stop requests. Currently, it's only sending Accounting-Start on hangup which is not intended. + +# Useful commands + +- sudo -u opensips /usr/sbin/opensips -f /etc/opensips/opensips.cfg -m 64 -M 4 -D +- cgr-engine -config_path=/usr/share/cgrates/tutorials/osips_diam/etc/cgrates -logger=*stdout +- cgr-loader -path /usr/share/cgrates/tutorials/osips_diam/tp/ -verbose +- freeDiameterd -dd -c /etc/freeDiameter/freeDiameter.conf +- pjsua_listen --help +- pjsua_listen --accounts 1001,1002 +- pjsua_call --help +- pjsua_call --from 1001 --to 1002 --dur 67 + diff --git a/data/tutorials/osips_diam/etc/cgrates/accounting.json b/data/tutorials/osips_diam/etc/cgrates/accounting.json new file mode 100644 index 000000000..739e399b5 --- /dev/null +++ b/data/tutorials/osips_diam/etc/cgrates/accounting.json @@ -0,0 +1,154 @@ +{ + +"diameter_agent": { + "request_processors": [ + { + "id": "AccountingStart", + "filters": ["*eq:~*req.Accounting-Record-Type:1"], + "flags": ["*initiate", "*accounts", "*log"], + "request_fields":[ + {"tag": "Category", "path": "*cgreq.Category", "type": "*constant", "value": "call"}, + {"tag": "RequestType", "path": "*cgreq.RequestType", "type": "*constant", + "value": "*prepaid", "mandatory": true}, + {"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*composed", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "OriginHost", "path": "*cgreq.OriginHost", "type": "*variable", + "value": "~*req.Origin-Host", "mandatory": true}, + {"tag": "Account", "path": "*cgreq.Account", "type": "*variable", + "value": "~*req.User-Name", "mandatory": true}, + {"tag": "Subject", "path": "*cgreq.Subject", "type": "*variable", + "value": "~*req.User-Name", "mandatory": true}, + {"tag": "Destination", "path": "*cgreq.Destination", "type": "*variable", + "value": "~*req.SIP-AVP", "mandatory": true}, + {"tag": "SetupTime", "path": "*cgreq.SetupTime", "type": "*variable", + "value": "~*req.Sip-Call-Setuptime", "mandatory": true}, + {"tag": "AnswerTime", "path": "*cgreq.AnswerTime", "type": "*variable", + "value": "~*req.Sip-Call-Created", "mandatory": true}, + {"tag": "RemoteAddr" , "path": "*cgreq.RemoteAddr", "type": "*variable", + "value": "~*vars.RemoteHost:s/(.*):\\d+/${1}/"} + ], + "reply_fields":[ + { + "tag": "Session-Id", + "path": "*rep.Session-Id", + "type": "*variable", + "value": "~*req.Session-Id" + }, + { + "tag": "Origin-Host", + "path": "*rep.Origin-Host", + "type": "*variable", + "value": "~*req.Origin-Host" + }, + { + "tag": "Origin-Realm", + "path": "*rep.Origin-Realm", + "type": "*variable", + "value": "~*req.Origin-Realm" + }, + { + "tag": "Accounting-Record-Type", + "path": "*rep.Accounting-Record-Type", + "type": "*variable", + "value": "~*req.Accounting-Record-Type" + }, + { + "tag": "Accounting-Record-Number", + "path": "*rep.Accounting-Record-Number", + "type": "*variable", + "value": "~*req.Accounting-Record-Number" + }, + { + "tag": "ResultCode", + "filters": ["*notempty:~*cgrep.Error:"], + "path": "*rep.Result-Code", + "type": "*constant", + "value": "5030", + "blocker": true + }, + { + "tag": "ResultCode", + "path": "*rep.Result-Code", + "type": "*constant", + "value": "2001" + } + ] + }, + { + "id": "AccountingStop", + "filters": ["*string:~*eq.Accounting-Record-Type:4"], + "flags": ["*terminate", "*accounts", "*cdrs", "*log"], + "request_fields":[ + {"tag": "Category", "path": "*cgreq.Category", "type": "*constant", "value": "call"}, + {"tag": "RequestType", "path": "*cgreq.RequestType", "type": "*constant", + "value": "*prepaid", "mandatory": true}, + {"tag": "OriginID", "path": "*cgreq.OriginID", "type": "*variable", + "value": "~*req.Session-Id", "mandatory": true}, + {"tag": "OriginHost", "path": "*cgreq.OriginHost", "type": "*variable", + "value": "~*req.Origin-Host", "mandatory": true}, + {"tag": "Account", "path": "*cgreq.Account", "type": "*variable", + "value": "~*req.User-Name", "mandatory": true}, + {"tag": "Subject", "path": "*cgreq.Subject", "type": "*variable", + "value": "~*req.User-Name", "mandatory": true}, + {"tag": "Destination", "path": "*cgreq.Destination", "type": "*variable", + "value": "~*req.Sip-To-Tag", "mandatory": true}, + {"tag": "SetupTime", "path": "*cgreq.SetupTime", "type": "*variable", + "value": "~*req.Sip-Call-Setuptime", "mandatory": true}, + {"tag": "AnswerTime", "path": "*cgreq.AnswerTime", "type": "*variable", + "value": "~*req.Sip-Call-Created", "mandatory": true}, + {"tag": "Usage", "path": "*cgreq.Usage", "type": "*variable", + "value": "~*req.Sip-Call-Duration", "mandatory": true}, + {"tag": "RemoteAddr" , "path": "*cgreq.RemoteAddr", "type": "*variable", + "value": "~*vars.RemoteHost:s/(.*):\\d+/${1}/"} + ], + "reply_fields":[ + { + "tag": "Session-Id", + "path": "*rep.Session-Id", + "type": "*variable", + "value": "~*req.Session-Id" + }, + { + "tag": "Origin-Host", + "path": "*rep.Origin-Host", + "type": "*variable", + "value": "~*req.Origin-Host" + }, + { + "tag": "Origin-Realm", + "path": "*rep.Origin-Realm", + "type": "*variable", + "value": "~*req.Origin-Realm" + }, + { + "tag": "Accounting-Record-Type", + "path": "*rep.Accounting-Record-Type", + "type": "*variable", + "value": "~*req.Accounting-Record-Type" + }, + { + "tag": "Accounting-Record-Number", + "path": "*rep.Accounting-Record-Number", + "type": "*variable", + "value": "~*req.Accounting-Record-Number" + }, + { + "tag": "ResultCode", + "filters": ["*notempty:~*cgrep.Error:"], + "path": "*rep.Result-Code", + "type": "*constant", + "value": "5030", + "blocker": true + }, + { + "tag": "ResultCode", + "path": "*rep.Result-Code", + "type": "*constant", + "value": "2001" + } + ] + } + ] +} + +} \ No newline at end of file diff --git a/data/tutorials/osips_diam/etc/cgrates/cc-postpaid.json b/data/tutorials/osips_diam/etc/cgrates/cc-postpaid.json new file mode 100644 index 000000000..47771554d --- /dev/null +++ b/data/tutorials/osips_diam/etc/cgrates/cc-postpaid.json @@ -0,0 +1,139 @@ +{ + +"diameter_agent": { + "request_processors": [ + { + "id": "SessionProcessCDR", + "filters": [ + "*string:~*vars.*cmd:CCR", + "*string:~*req.CC-Request-Type:4", + "*prefix:~*req.Service-Context-Id:voice" + ], + "flags": ["*cdrs", "*log"], + "request_fields":[ + { + "tag": "ToR", + "path": "*cgreq.ToR", + "type": "*constant", + "value": "*voice" + }, + { + "tag": "OriginID", + "path": "*cgreq.OriginID", + "type": "*variable", + "value": "~*req.Session-Id", + "mandatory": true + }, + { + "tag": "OriginHost", + "path": "*cgreq.OriginHost", + "type": "*variable", + "value": "~*req.Origin-Host", + "mandatory": true + }, + { + "tag": "RequestType", + "path": "*cgreq.RequestType", + "type": "*constant", + "value": "*postpaid" + }, + { + "tag": "Account", + "path": "*cgreq.Account", + "type": "*variable", + "value": "~*req.Service-Information.IMS-Information.Calling-Party-Address", + "mandatory": true + }, + { + "tag": "Destination", + "path": "*cgreq.Destination", + "type": "*variable", + "value": "~*req.Service-Information.IMS-Information.Called-Party-Address", + "mandatory": true + }, + { + "tag": "AnswerTime", + "path": "*cgreq.AnswerTime", + "type": "*variable", + "value": "~*req.Event-Timestamp", + "mandatory": true + }, + { + "tag": "Usage", + "path": "*cgreq.Usage", + "type": "*cc_usage", + "mandatory": true, + "value": "~*req.CC-Request-Number;~*req.Used-Service-Unit.CC-Time:s/(.*)/${1}s/;5m" + }, + { + "tag": "LastUsed", + "path": "*cgreq.LastUsed", + "type": "*variable", + "value": "~*req.Used-Service-Unit.CC-Time:s/(.*)/${1}s/", + "mandatory": true + }, + { + "tag": "SubscriberID", + "path": "*cgreq.SubscriberId", + "type": "*variable", + "value": "~*req.Subscription-Id.Subscription-Id-Data", + "mandatory": true + } + ], + "reply_fields":[ + { + "tag": "Session-Id", + "path": "*rep.Session-Id", + "type": "*variable", + "value": "~*req.Session-Id" + }, + { + "tag": "Origin-Host", + "path": "*rep.Origin-Host", + "type": "*variable", + "value": "~*req.Origin-Host" + }, + { + "tag": "Origin-Realm", + "path": "*rep.Origin-Realm", + "type": "*variable", + "value": "~*req.Origin-Realm" + }, + { + "tag": "Auth-Application-Id", + "path": "*rep.Auth-Application-Id", + "type": "*variable", + "value": "~*req.Auth-Application-Id" + }, + { + "tag": "CC-Request-Type", + "path": "*rep.CC-Request-Type", + "type": "*variable", + "value": "~*req.CC-Request-Type" + }, + { + "tag": "CC-Request-Number", + "path": "*rep.CC-Request-Number", + "type": "*variable", + "value": "~*req.CC-Request-Number" + }, + { + "tag": "ResultCode", + "filters": ["*notempty:~*cgrep.Error:"], + "path": "*rep.Result-Code", + "type": "*constant", + "value": "5030", + "blocker": true + }, + { + "tag": "ResultCode", + "path": "*rep.Result-Code", + "type": "*constant", + "value": "2001" + } + ] + } + ] +} + +} \ No newline at end of file diff --git a/data/tutorials/osips_diam/etc/cgrates/cc-prepaid-auth.json b/data/tutorials/osips_diam/etc/cgrates/cc-prepaid-auth.json new file mode 100644 index 000000000..3b973b14e --- /dev/null +++ b/data/tutorials/osips_diam/etc/cgrates/cc-prepaid-auth.json @@ -0,0 +1,139 @@ +{ + +"diameter_agent": { + "request_processors": [ + { + "id": "SessionAuth", + "filters": [ + "*string:~*vars.*cmd:CCR", + "*string:~*req.CC-Request-Type:1", + "*prefix:~*req.Service-Context-Id:voice" + ], + "flags": ["*authorize", "*accounts", "*log"], + "request_fields":[ + { + "tag": "ToR", + "path": "*cgreq.ToR", + "type": "*constant", + "value": "*voice" + }, + { + "tag": "OriginID", + "path": "*cgreq.OriginID", + "type": "*variable", + "value": "~*req.Session-Id", + "mandatory": true + }, + { + "tag": "OriginHost", + "path": "*cgreq.OriginHost", + "type": "*variable", + "value": "~*req.Origin-Host", + "mandatory": true + }, + { + "tag": "RequestType", + "path": "*cgreq.RequestType", + "type": "*constant", + "value": "*prepaid", + "mandatory": true + }, + { + "tag": "Account", + "path": "*cgreq.Account", + "type": "*variable", + "value": "~*req.Service-Information.IMS-Information.Calling-Party-Address", + "mandatory": true + }, + { + "tag": "Destination", + "path": "*cgreq.Destination", + "type": "*variable", + "value": "~*req.Service-Information.IMS-Information.Called-Party-Address", + "mandatory": true + }, + { + "tag": "SetupTime", + "path": "*cgreq.SetupTime", + "type": "*variable", + "value": "~*req.Event-Timestamp", + "mandatory": true + }, + { + "tag": "Usage", + "path": "*cgreq.Usage", + "type": "*variable", + "mandatory": true, + "value": "~*req.Requested-Service-Unit.CC-Time;s" + }, + { + "tag": "SubscriberID", + "path": "*cgreq.SubscriberId", + "type": "*variable", + "value": "~*req.Subscription-Id.Subscription-Id-Data", + "mandatory": true + } + ], + "reply_fields":[ + { + "tag": "Session-Id", + "path": "*rep.Session-Id", + "type": "*variable", + "value": "~*req.Session-Id" + }, + { + "tag": "Origin-Host", + "path": "*rep.Origin-Host", + "type": "*variable", + "value": "~*req.Origin-Host" + }, + { + "tag": "Origin-Realm", + "path": "*rep.Origin-Realm", + "type": "*variable", + "value": "~*req.Origin-Realm" + }, + { + "tag": "Auth-Application-Id", + "path": "*rep.Auth-Application-Id", + "type": "*variable", + "value": "~*req.Auth-Application-Id" + }, + { + "tag": "CC-Request-Type", + "path": "*rep.CC-Request-Type", + "type": "*variable", + "value": "~*req.CC-Request-Type" + }, + { + "tag": "CC-Request-Number", + "path": "*rep.CC-Request-Number", + "type": "*variable", + "value": "~*req.CC-Request-Number" + }, + { + "tag": "ResultCode", + "filters": ["*notempty:~*cgrep.Error:"], + "path": "*rep.Result-Code", + "type": "*constant", + "value": "5030", + "blocker": true + }, + { + "tag": "ResultCode", + "path": "*rep.Result-Code", + "type": "*constant", + "value": "2001" + }, + { + "tag": "Granted-Units", + "path": "*rep.Granted-Service-Unit.CC-Time", + "type": "*variable", + "value": "~*cgrep.MaxUsage{*duration_seconds}" + } + ] + } + ] +} + +} \ No newline at end of file diff --git a/data/tutorials/osips_diam/etc/cgrates/cc-prepaid-init.json b/data/tutorials/osips_diam/etc/cgrates/cc-prepaid-init.json new file mode 100644 index 000000000..7bbd39886 --- /dev/null +++ b/data/tutorials/osips_diam/etc/cgrates/cc-prepaid-init.json @@ -0,0 +1,126 @@ +{ + +"diameter_agent": { + "request_processors": [ + { + "id": "SessionInit", + "filters": [ + "*string:~*vars.*cmd:CCR", + "*string:~*req.CC-Request-Type:2", + "*prefix:~*req.Service-Context-Id:voice" + ], + "flags": ["*initiate", "*accounts", "*log"], + "request_fields":[ + { + "tag": "ToR", + "path": "*cgreq.ToR", + "type": "*constant", + "value": "*voice" + }, + { + "tag": "OriginID", + "path": "*cgreq.OriginID", + "type": "*variable", + "value": "~*req.Session-Id", + "mandatory": true + }, + { + "tag": "OriginHost", + "path": "*cgreq.OriginHost", + "type": "*variable", + "value": "~*req.Origin-Host", + "mandatory": true + }, + { + "tag": "RequestType", + "path": "*cgreq.RequestType", + "type": "*constant", + "value": "*prepaid", + "mandatory": true + }, + { + "tag": "Account", + "path": "*cgreq.Account", + "type": "*variable", + "value": "~*req.Service-Information.IMS-Information.Calling-Party-Address", + "mandatory": true + }, + { + "tag": "Destination", + "path": "*cgreq.Destination", + "type": "*variable", + "value": "~*req.Service-Information.IMS-Information.Called-Party-Address", + "mandatory": true + }, + { + "tag": "AnswerTime", + "path": "*cgreq.AnswerTime", + "type": "*variable", + "value": "~*req.Event-Timestamp", + "mandatory": true + }, + { + "tag": "SubscriberID", + "path": "*cgreq.SubscriberId", + "type": "*variable", + "value": "~*req.Subscription-Id.Subscription-Id-Data", + "mandatory": true + } + ], + "reply_fields":[ + { + "tag": "Session-Id", + "path": "*rep.Session-Id", + "type": "*variable", + "value": "~*req.Session-Id" + }, + { + "tag": "Origin-Host", + "path": "*rep.Origin-Host", + "type": "*variable", + "value": "~*req.Origin-Host" + }, + { + "tag": "Origin-Realm", + "path": "*rep.Origin-Realm", + "type": "*variable", + "value": "~*req.Origin-Realm" + }, + { + "tag": "Auth-Application-Id", + "path": "*rep.Auth-Application-Id", + "type": "*variable", + "value": "~*req.Auth-Application-Id" + }, + { + "tag": "CC-Request-Type", + "path": "*rep.CC-Request-Type", + "type": "*variable", + "value": "~*req.CC-Request-Type" + }, + { + "tag": "CC-Request-Number", + "path": "*rep.CC-Request-Number", + "type": "*variable", + "value": "~*req.CC-Request-Number" + }, + { + "tag": "ResultCode", + "filters": ["*notempty:~*cgrep.Error:"], + "path": "*rep.Result-Code", + "type": "*constant", + "value": "5030", + "blocker": true + }, + { + "tag": "ResultCode", + "path": "*rep.Result-Code", + "type": "*constant", + "value": "2001" + } + ] + } + ] +} + +} \ No newline at end of file diff --git a/data/tutorials/osips_diam/etc/cgrates/cc-prepaid-terminate.json b/data/tutorials/osips_diam/etc/cgrates/cc-prepaid-terminate.json new file mode 100644 index 000000000..affe5c657 --- /dev/null +++ b/data/tutorials/osips_diam/etc/cgrates/cc-prepaid-terminate.json @@ -0,0 +1,145 @@ +{ + +"diameter_agent": { + "request_processors": [ + { + "id": "SessionTerminate", + "filters": [ + "*string:~*vars.*cmd:CCR", + "*string:~*req.CC-Request-Type:3", + "*prefix:~*req.Service-Context-Id:voice" + ], + "flags": ["*terminate", "*accounts", "*cdrs", "*log"], + "request_fields":[ + { + "tag": "ToR", + "path": "*cgreq.ToR", + "type": "*constant", + "value": "*voice" + }, + { + "tag": "OriginID", + "path": "*cgreq.OriginID", + "type": "*variable", + "value": "~*req.Session-Id", + "mandatory": true + }, + { + "tag": "OriginHost", + "path": "*cgreq.OriginHost", + "type": "*variable", + "value": "~*req.Origin-Host", + "mandatory": true + }, + { + "tag": "RequestType", + "path": "*cgreq.RequestType", + "type": "*constant", + "value": "*prepaid" + }, + { + "tag": "Account", + "path": "*cgreq.Account", + "type": "*variable", + "value": "~*req.Service-Information.IMS-Information.Calling-Party-Address", + "mandatory": true + }, + { + "tag": "Destination", + "path": "*cgreq.Destination", + "type": "*variable", + "value": "~*req.Service-Information.IMS-Information.Called-Party-Address", + "mandatory": true + }, + { + "tag": "AnswerTime", + "path": "*cgreq.AnswerTime", + "type": "*variable", + "value": "~*req.Event-Timestamp", + // "value": "~*req.Service-Information.IMS-Information.Time-Stamps.SIP-Request-Response", + "mandatory": true + }, + + // would using these assume that an update CCR request is sent at every debit interval? + // + // For now I commented them, and let have this just terminate and debit the cached session. + // { + // "tag": "Usage", + // "path": "*cgreq.Usage", + // "type": "*cc_usage", + // "mandatory": true, + // "value": "~*req.CC-Request-Number;~*req.Used-Service-Unit.CC-Time:s/(.*)/${1}s/;5s" + // }, + // { + // "tag": "LastUsed", + // "path": "*cgreq.LastUsed", + // "type": "*variable", + // "value": "~*req.Used-Service-Unit.CC-Time:s/(.*)/${1}s/", + // "mandatory": true + // }, + + { + "tag": "SubscriberID", + "path": "*cgreq.SubscriberId", + "type": "*variable", + "value": "~*req.Subscription-Id.Subscription-Id-Data", + "mandatory": true + } + ], + "reply_fields":[ + { + "tag": "Session-Id", + "path": "*rep.Session-Id", + "type": "*variable", + "value": "~*req.Session-Id" + }, + { + "tag": "Origin-Host", + "path": "*rep.Origin-Host", + "type": "*variable", + "value": "~*req.Origin-Host" + }, + { + "tag": "Origin-Realm", + "path": "*rep.Origin-Realm", + "type": "*variable", + "value": "~*req.Origin-Realm" + }, + { + "tag": "Auth-Application-Id", + "path": "*rep.Auth-Application-Id", + "type": "*variable", + "value": "~*req.Auth-Application-Id" + }, + { + "tag": "CC-Request-Type", + "path": "*rep.CC-Request-Type", + "type": "*variable", + "value": "~*req.CC-Request-Type" + }, + { + "tag": "CC-Request-Number", + "path": "*rep.CC-Request-Number", + "type": "*variable", + "value": "~*req.CC-Request-Number" + }, + { + "tag": "ResultCode", + "filters": ["*notempty:~*cgrep.Error:"], + "path": "*rep.Result-Code", + "type": "*constant", + "value": "5030", + "blocker": true + }, + { + "tag": "ResultCode", + "path": "*rep.Result-Code", + "type": "*constant", + "value": "2001" + } + ] + } + ] +} + +} \ No newline at end of file diff --git a/data/tutorials/osips_diam/etc/cgrates/cgrates.json b/data/tutorials/osips_diam/etc/cgrates/cgrates.json new file mode 100644 index 000000000..daf8a1061 --- /dev/null +++ b/data/tutorials/osips_diam/etc/cgrates/cgrates.json @@ -0,0 +1,75 @@ +{ + +"general": { + "log_level": 7 +}, + +"listen": { + "rpc_json": ":2012", + "rpc_gob": ":2013", + "http": ":2080" +}, + + +"stor_db": { + "db_password": "CGRateS.org" +}, + +"schedulers": { + "enabled": true +}, + +"rals": { + "enabled": true +}, + +"cdrs": { + "enabled": true, + "chargers_conns": ["*localhost"], + "rals_conns": ["*localhost"], + "sessions_cost_retries": 5 +}, + +"chargers": { + "enabled": true, + "attributes_conns": ["*localhost"] +}, + +"sessions": { + "enabled": true, + "listen_bijson": "127.0.0.1:2014", + "chargers_conns": ["*localhost"], + "rals_conns": ["*localhost"], + "cdrs_conns": ["*localhost"], + "attributes_conns": ["*localhost"], + "debit_interval": "5s" +}, + +"attributes": { + "enabled": true, + "string_indexed_fields": ["*req.Account"] +}, + +"apiers": { + "enabled": true, + "scheduler_conns": ["*localhost"] +}, + +"diameter_agent": { + "enabled": true, + "listen": "192.168.122.42:3868", + "listen_net": "tcp", + "dictionaries_path": "/usr/share/cgrates/diameter/dict/", + "sessions_conns": ["*bijson_localhost"], + "origin_host": "server.diameter.test", + "origin_realm": "diameter.test", + "vendor_id": 0, + "product_name": "CGRateS", + "concurrent_requests": -1, + "synced_conn_requests": false, + "asr_template": "*asr", + "rar_template": "*rar", + "forced_disconnect": "*asr" +} + +} diff --git a/data/tutorials/osips_diam/etc/opensips/dictionary.opensips b/data/tutorials/osips_diam/etc/opensips/dictionary.opensips new file mode 100644 index 000000000..e88592fd5 --- /dev/null +++ b/data/tutorials/osips_diam/etc/opensips/dictionary.opensips @@ -0,0 +1,94 @@ +APPLICATION 4 Charging Control + +ATTRIBUTE Result-Code 268 unsigned32 +ATTRIBUTE Value-Digits 447 integer64 +ATTRIBUTE Exponent 429 integer32 +ATTRIBUTE Unit-Value 445 grouped +{ + Value-Digits | REQUIRED | 1 + Exponent | REQUIRED | 1 +} +ATTRIBUTE Currency-Code 425 unsigned32 +ATTRIBUTE CC-Money 413 grouped +{ + Unit-Value | REQUIRED | 1 + Currency-Code | REQUIRED | 1 +} +ATTRIBUTE CC-Request-Number 415 unsigned32 +ATTRIBUTE CC-Request-Type 416 unsigned32 +ATTRIBUTE CC-Time 420 unsigned32 +ATTRIBUTE Granted-Service-Unit 431 grouped +{ + CC-Time | OPTIONAL | 1 + CC-Money | OPTIONAL | 1 +} +ATTRIBUTE Requested-Service-Unit 437 grouped +{ + CC-Time | OPTIONAL | 1 + CC-Money | OPTIONAL | 1 +} +ATTRIBUTE Subscription-Id-Type 450 unsigned32 +ATTRIBUTE Subscription-Id-Data 444 string +ATTRIBUTE Subscription-Id 443 grouped +{ + Subscription-Id-Type | REQUIRED | 1 + Subscription-Id-Data | REQUIRED | 1 +} +ATTRIBUTE Used-Service-Unit 446 grouped +{ + CC-Time | OPTIONAL | 1 + CC-Money | OPTIONAL | 1 +} +ATTRIBUTE Service-Context-Id 461 string + +VENDOR 10415 TGPP +ATTRIBUTE Calling-Party-Address 831 string 10415 +ATTRIBUTE Called-Party-Address 832 string 10415 +ATTRIBUTE SIP-Request-Timestamp 2301 unsigned32 10415 +ATTRIBUTE SIP-Response-Timestamp 834 string 10415 +ATTRIBUTE Time-Stamps 833 grouped 10415 +{ + SIP-Request-Timestamp | OPTIONAL | 1 + SIP-Response-Timestamp | OPTIONAL | 1 +} +ATTRIBUTE IMS-Information 876 grouped 10415 +{ + Calling-Party-Address | OPTIONAL | 1 + Called-Party-Address | OPTIONAL | 1 + Time-Stamps | OPTIONAL | 1 +} +ATTRIBUTE Service-Information 873 grouped 10415 +{ + IMS-Information | OPTIONAL | 1 +} + +REQUEST 272 Credit-Control-Request +{ + Session-Id | REQUIRED | 1 + Origin-Host | REQUIRED | 1 + Origin-Realm | REQUIRED | 1 + Destination-Realm | REQUIRED | 1 + Auth-Application-Id | REQUIRED | 1 + Service-Context-Id | REQUIRED | 1 + CC-Request-Type | REQUIRED | 1 + CC-Request-Number | REQUIRED | 1 + Event-Timestamp | OPTIONAL | 1 + User-Name | OPTIONAL | 1 + Subscription-Id | OPTIONAL | 1 + Requested-Service-Unit | OPTIONAL | 1 + Used-Service-Unit | OPTIONAL | 1 + Service-Information | OPTIONAL | 1 + Termination-Cause | OPTIONAL | 1 +} + +ANSWER 272 Credit-Control-Answer +{ + Result-Code | OPTIONAL | 1 + Session-Id | OPTIONAL | 1 + Origin-Host | OPTIONAL | 1 + Origin-Realm | OPTIONAL | 1 + CC-Request-Type | OPTIONAL | 1 + CC-Request-Number | OPTIONAL | 1 + Granted-Service-Unit | OPTIONAL | 1 + Route-Record | OPTIONAL | 1 +} \ No newline at end of file diff --git a/data/tutorials/osips_diam/etc/opensips/opensips_acc.cfg b/data/tutorials/osips_diam/etc/opensips/opensips_acc.cfg new file mode 100644 index 000000000..1da12b68d --- /dev/null +++ b/data/tutorials/osips_diam/etc/opensips/opensips_acc.cfg @@ -0,0 +1,278 @@ +# +# OpenSIPS residential configuration script +# by OpenSIPS Solutions +# +# This script was generated via "make menuconfig", from +# the "Residential" scenario. +# You can enable / disable more features / functionalities by +# re-generating the scenario with different options.# +# +# Please refer to the Core CookBook at: +# https://opensips.org/Resources/DocsCookbooks +# for a explanation of possible statements, functions and parameters. +# + + +####### Global Parameters ######### + +debug_mode=no +log_stdout=yes +log_level=3 +xlog_level=3 +stderror_enabled=no +syslog_enabled=yes +syslog_facility=LOG_LOCAL0 + +udp_workers=4 + +socket=udp:enp0s3:5060 +socket=udp:127.0.0.1:5060 + +####### Modules Section ######## + +#set module path +mpath="/usr/lib/x86_64-linux-gnu/opensips/modules/" + +#### SIGNALING module +loadmodule "signaling.so" + +#### StateLess module +loadmodule "sl.so" + +#### Transaction Module +loadmodule "tm.so" +modparam("tm", "fr_timeout", 5) +modparam("tm", "fr_inv_timeout", 30) +modparam("tm", "restart_fr_on_each_reply", 0) +modparam("tm", "onreply_avp_mode", 1) + +#### Record Route Module +loadmodule "rr.so" +modparam("rr", "append_fromtag", 0) + +#### MAX ForWarD module +loadmodule "maxfwd.so" + +#### SIP MSG OPerationS module +loadmodule "sipmsgops.so" + +#### FIFO Management Interface +loadmodule "mi_fifo.so" +modparam("mi_fifo", "fifo_name", "/run/opensips/opensips_fifo") +modparam("mi_fifo", "fifo_mode", 0666) + +#### USeR LOCation module +loadmodule "usrloc.so" +modparam("usrloc", "nat_bflag", "NAT") +modparam("usrloc", "working_mode_preset", "single-instance-no-db") + +#### REGISTRAR module +loadmodule "registrar.so" +modparam("registrar", "tcp_persistent_flag", "TCP_PERSISTENT") +#modparam("registrar", "max_contacts", 10) + +#### ACCounting module +loadmodule "acc.so" +modparam("acc", "early_media", 0) +modparam("acc", "report_cancels", 0) +modparam("acc", "detect_direction", 0) +modparam("acc", "aaa_url", "diameter:/etc/freeDiameter/freeDiameter.conf") + +# Adding some AVPs to identify account and destination. No idea what AVP to use +# for destination. Went for SIP-AVP. +modparam("acc", "extra_fields", "aaa: account -> User-Name ; destination -> SIP-AVP") + +# Sip-Session +modparam("acc", "service_type", 15) + +loadmodule "proto_udp.so" + +#### Dialog module +loadmodule "dialog.so" + +#### Auth modules (not used in this cfg) +loadmodule "auth.so" +loadmodule "auth_aaa.so" +modparam("auth_aaa", "aaa_url", "diameter:/etc/freeDiameter/freeDiameter.conf") + +#### Diameter module +loadmodule "aaa_diameter.so" +modparam("aaa_diameter", "fd_log_level", 0) +modparam("aaa_diameter", "realm", "diameter.test") +modparam("aaa_diameter", "peer_identity", "server") + +####### Routing Logic ######## + +# main request routing logic + +route{ + + if (!mf_process_maxfwd_header(10)) { + send_reply(483,"Too Many Hops"); + exit; + } + + if (has_totag()) { + + # handle hop-by-hop ACK (no routing required) + if ( is_method("ACK") && t_check_trans() ) { + t_relay(); + exit; + } + + # sequential request within a dialog should + # take the path determined by record-routing + if ( !loose_route() ) { + # we do record-routing for all our traffic, so we should not + # receive any sequential requests without Route hdr. + send_reply(404,"Not here"); + exit; + } + + if (is_method("BYE")) { + # do accounting even if the transaction fails + do_accounting("log","failed"); + } + + # route it out to whatever destination was set by loose_route() + # in $du (destination URI). + route(relay); + exit; + } + + # CANCEL processing + if (is_method("CANCEL")) { + if (t_check_trans()) + t_relay(); + exit; + } + + # absorb retransmissions, but do not create transaction + t_check_trans(); + + if ( !(is_method("REGISTER") ) ) { + + if (is_myself("$fd")) { + + } else { + # if caller is not local, then called number must be local + + if (!is_myself("$rd")) { + send_reply(403,"Relay Forbidden"); + exit; + } + } + + } + + # preloaded route checking + if (loose_route()) { + xlog("L_ERR", + "Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]"); + if (!is_method("ACK")) + send_reply(403,"Preload Route denied"); + exit; + } + + # record routing + if (!is_method("REGISTER|MESSAGE")) + record_route(); + + # account only INVITEs + if (is_method("INVITE")) { + + # if (!aaa_proxy_authorize("$fd")){ + # proxy_challenge("$fd"); + # exit; + # } + + $acc_extra(account) = $fU; + $acc_extra(destination) = $rU; + + # this sends an accounting start on hangup/timeout. Not + # sure how to set up accounting to send Accounting-Start + # on answer and Accounting-Stop on hangup. + do_accounting("aaa", "cdr"); + } + + if (!is_myself("$rd")) { + append_hf("P-hint: outbound\r\n"); + + route(relay); + } + + # requests for my domain + + if (is_method("PUBLISH|SUBSCRIBE")) { + send_reply(503, "Service Unavailable"); + exit; + } + + if (is_method("REGISTER")) { + # store the registration and generate a SIP reply + if (!save("location")) + xlog("failed to register AoR $tu\n"); + + exit; + } + + if ($rU==NULL) { + # request with no Username in RURI + send_reply(484,"Address Incomplete"); + exit; + } + + # do lookup with method filtering + if (!lookup("location","method-filtering")) { + t_reply(404, "Not Found"); + exit; + } + + do_accounting("log","missed"); + route(relay); +} + +route[relay] { + # for INVITEs enable some additional helper routes + if (is_method("INVITE")) { + t_on_branch("per_branch_ops"); + t_on_reply("handle_nat"); + t_on_failure("missed_call"); + } + + if (!t_relay()) { + send_reply(500,"Internal Error"); + } + exit; +} + + + + +branch_route[per_branch_ops] { + xlog("new branch at $ru\n"); +} + + +onreply_route[handle_nat] { + xlog("incoming reply\n"); +} + + +failure_route[missed_call] { + if (t_was_cancelled()) { + exit; + } + + # uncomment the following lines if you want to block client + # redirect based on 3xx replies. + ##if (t_check_status("3[0-9][0-9]")) { + ##t_reply(404,"Not found"); + ## exit; + ##} + + +} + + + diff --git a/data/tutorials/osips_diam/etc/opensips/opensips_ccr_postpaid.cfg b/data/tutorials/osips_diam/etc/opensips/opensips_ccr_postpaid.cfg new file mode 100644 index 000000000..5064cbc1c --- /dev/null +++ b/data/tutorials/osips_diam/etc/opensips/opensips_ccr_postpaid.cfg @@ -0,0 +1,309 @@ +# +# OpenSIPS residential configuration script +# by OpenSIPS Solutions +# +# This script was generated via "make menuconfig", from +# the "Residential" scenario. +# You can enable / disable more features / functionalities by +# re-generating the scenario with different options.# +# +# Please refer to the Core CookBook at: +# https://opensips.org/Resources/DocsCookbooks +# for a explanation of possible statements, functions and parameters. +# + + +####### Global Parameters ######### + +debug_mode=no +log_stdout=yes +log_level=3 +xlog_level=3 +stderror_enabled=no +syslog_enabled=yes +syslog_facility=LOG_LOCAL0 + +udp_workers=4 + +socket=udp:enp0s3:5060 +socket=udp:127.0.0.1:5060 + +####### Modules Section ######## + +#set module path +mpath="/usr/lib/x86_64-linux-gnu/opensips/modules/" + +#### SIGNALING module +loadmodule "signaling.so" + +#### StateLess module +loadmodule "sl.so" + +#### Transaction Module +loadmodule "tm.so" +modparam("tm", "fr_timeout", 5) +modparam("tm", "fr_inv_timeout", 30) +modparam("tm", "restart_fr_on_each_reply", 0) +modparam("tm", "onreply_avp_mode", 1) + +#### Record Route Module +loadmodule "rr.so" +modparam("rr", "append_fromtag", 0) + +#### MAX ForWarD module +loadmodule "maxfwd.so" + +#### SIP MSG OPerationS module +loadmodule "sipmsgops.so" + +#### FIFO Management Interface +loadmodule "mi_fifo.so" +modparam("mi_fifo", "fifo_name", "/run/opensips/opensips_fifo") +modparam("mi_fifo", "fifo_mode", 0666) + +#### USeR LOCation module +loadmodule "usrloc.so" +modparam("usrloc", "nat_bflag", "NAT") +modparam("usrloc", "working_mode_preset", "single-instance-no-db") + +#### REGISTRAR module +loadmodule "registrar.so" +modparam("registrar", "tcp_persistent_flag", "TCP_PERSISTENT") + +loadmodule "proto_udp.so" + +#### Dialog module +loadmodule "dialog.so" + +#### Exec module +loadmodule "exec.so" + + +#### Diameter module +loadmodule "aaa_diameter.so" +modparam("aaa_diameter", "fd_log_level", 0) +modparam("aaa_diameter", "realm", "diameter.test") +modparam("aaa_diameter", "peer_identity", "server") +modparam("aaa_diameter", "aaa_url", "diameter:/etc/freeDiameter/freeDiameter.conf;extra-avps-file:/etc/opensips/dictionary.opensips") + +####### Routing Logic ######## + +# main request routing logic + +route{ + + if (!mf_process_maxfwd_header(10)) { + send_reply(483,"Too Many Hops"); + exit; + } + + if (has_totag()) { + + # handle hop-by-hop ACK (no routing required) + if ( is_method("ACK") && t_check_trans() ) { + t_relay(); + exit; + } + + # sequential request within a dialog should + # take the path determined by record-routing + if ( !loose_route() ) { + # we do record-routing for all our traffic, so we should not + # receive any sequential requests without Route hdr. + send_reply(404,"Not here"); + exit; + } + + # if (is_method("BYE")) { + # # do accounting even if the transaction fails + # do_accounting("log","failed"); + # } + + # route it out to whatever destination was set by loose_route() + # in $du (destination URI). + route(relay); + exit; + } + + # CANCEL processing + if (is_method("CANCEL")) { + if (t_check_trans()) + t_relay(); + exit; + } + + # absorb retransmissions, but do not create transaction + t_check_trans(); + + if ( !(is_method("REGISTER") ) ) { + + if (is_myself("$fd")) { + + } else { + # if caller is not local, then called number must be local + + if (!is_myself("$rd")) { + send_reply(403,"Relay Forbidden"); + exit; + } + } + + } + + # preloaded route checking + if (loose_route()) { + xlog("L_ERR", + "Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]"); + if (!is_method("ACK")) + send_reply(403,"Preload Route denied"); + exit; + } + + # record routing + if (!is_method("REGISTER|MESSAGE")) + record_route(); + + # account only INVITEs + if (is_method("INVITE")) { + + create_dialog(); + $dlg_val(callid) = $ci; + $dlg_val(originhost) = $(ou{uri.host}); + $dlg_val(originrealm) = $fd; + $dlg_val(setuptime) = $Ts; + $dlg_val(account) = $fU; + $dlg_val(destination) = $rU; + + # we need an answer handler to record answertime for cdr processing + dlg_on_answer("handle_answer"); + + # would probably be a good idea to have a timeout handler also + dlg_on_hangup("handle_hangup"); + } + + if (!is_myself("$rd")) { + append_hf("P-hint: outbound\r\n"); + + route(relay); + } + + # requests for my domain + + if (is_method("PUBLISH|SUBSCRIBE")) { + send_reply(503, "Service Unavailable"); + exit; + } + + if (is_method("REGISTER")) { + # store the registration and generate a SIP reply + if (!save("location")) + xlog("failed to register AoR $tu\n"); + + exit; + } + + if ($rU==NULL) { + # request with no Username in RURI + send_reply(484,"Address Incomplete"); + exit; + } + + # do lookup with method filtering + if (!lookup("location","method-filtering")) { + t_reply(404, "Not Found"); + exit; + } + + route(relay); +} + +route[handle_answer] { + $dlg_val(answertime) = $Ts; + xlog("$$dlg_val(answertime) 1 = $dlg_val(answertime)\n"); +} + +route[handle_hangup] { + $var(duration) = $Ts - $dlg_val(answertime); + exec("/usr/share/cgrates/tutorials/osips_diam/etc/opensips/unix2ntp", "$dlg_val(answertime)", $var(answertime32bit), , ); + $var(payload) = "[ + { \"Session-Id\": \"" + $dlg_val(callid) + "\" }, + { \"Origin-Host\": \"client.diameter.test\" }, + { \"Origin-Realm\": \"diameter.test\" }, + { \"Destination-Realm\": \"diameter.test\" }, + { \"Service-Context-Id\": \"voice@OsipsDiamCCR\" }, + { \"CC-Request-Type\": 4 }, + { \"CC-Request-Number\": 0 }, + { \"Termination-Cause\": 1 }, + { \"Event-Timestamp\": \"" + $var(answertime32bit) + "\" }, + { \"User-Name\": \"" + $dlg_val(account) + "\" }, + { \"Subscription-Id\": [ + { \"Subscription-Id-Type\": 0 }, + { \"Subscription-Id-Data\": \"" + $dlg_val(account) + "\" } + ]}, + { \"Requested-Service-Unit\": [ + {\"CC-Time\": 0} + ]}, + { \"Used-Service-Unit\": [ + {\"CC-Time\": " + $var(duration) + " } + ]}, + { \"Service-Information\": [ + {\"IMS-Information\": [ + { \"Calling-Party-Address\": \"" + $dlg_val(account) + "\" }, + { \"Called-Party-Address\": \"" + $dlg_val(destination) + "\" } + ]} + ]} + ]"; + + dm_send_request(4, 272, $var(payload), $var(rpl_avps)); + if ($rc < 1) { + xlog("error processing diameter request (returned $rc), received reply: $var(rpl_avps)"); + exit; + } + route(relay); +} + + +route[relay] { + # for INVITEs enable some additional helper routes + if (is_method("INVITE")) { + t_on_branch("per_branch_ops"); + t_on_reply("handle_nat"); + t_on_failure("missed_call"); + } + + if (!t_relay()) { + send_reply(500,"Internal Error"); + } + exit; +} + + + + +branch_route[per_branch_ops] { + xlog("new branch at $ru\n"); +} + + +onreply_route[handle_nat] { + xlog("incoming reply\n"); +} + + +failure_route[missed_call] { + if (t_was_cancelled()) { + exit; + } + + # uncomment the following lines if you want to block client + # redirect based on 3xx replies. + ##if (t_check_status("3[0-9][0-9]")) { + ##t_reply(404,"Not found"); + ## exit; + ##} + + +} + + + diff --git a/data/tutorials/osips_diam/etc/opensips/opensips_ccr_prepaid.cfg b/data/tutorials/osips_diam/etc/opensips/opensips_ccr_prepaid.cfg new file mode 100644 index 000000000..a53ca3d74 --- /dev/null +++ b/data/tutorials/osips_diam/etc/opensips/opensips_ccr_prepaid.cfg @@ -0,0 +1,440 @@ +# +# OpenSIPS residential configuration script +# by OpenSIPS Solutions +# +# This script was generated via "make menuconfig", from +# the "Residential" scenario. +# You can enable / disable more features / functionalities by +# re-generating the scenario with different options.# +# +# Please refer to the Core CookBook at: +# https://opensips.org/Resources/DocsCookbooks +# for a explanation of possible statements, functions and parameters. +# + + +####### Global Parameters ######### + +debug_mode=no +log_stdout = yes +log_level=3 +xlog_level=3 +stderror_enabled=no +syslog_enabled=yes +syslog_facility=LOG_LOCAL0 + +udp_workers=4 + +socket=udp:enp0s3:5060 +socket=udp:127.0.0.1:5060 + +####### Modules Section ######## + +#set module path +mpath="/usr/lib/x86_64-linux-gnu/opensips/modules/" + +#### SIGNALING module +loadmodule "signaling.so" + +#### StateLess module +loadmodule "sl.so" + +#### Transaction Module +loadmodule "tm.so" +modparam("tm", "fr_timeout", 5) +modparam("tm", "fr_inv_timeout", 30) +modparam("tm", "restart_fr_on_each_reply", 0) +modparam("tm", "onreply_avp_mode", 1) + +#### Record Route Module +loadmodule "rr.so" +modparam("rr", "append_fromtag", 0) + +#### MAX ForWarD module +loadmodule "maxfwd.so" + +#### SIP MSG OPerationS module +loadmodule "sipmsgops.so" + +#### FIFO Management Interface +loadmodule "mi_fifo.so" +modparam("mi_fifo", "fifo_name", "/run/opensips/opensips_fifo") +modparam("mi_fifo", "fifo_mode", 0666) + +#### USeR LOCation module +loadmodule "usrloc.so" +modparam("usrloc", "nat_bflag", "NAT") +modparam("usrloc", "working_mode_preset", "single-instance-no-db") + +#### REGISTRAR module +loadmodule "registrar.so" +modparam("registrar", "tcp_persistent_flag", "TCP_PERSISTENT") +#modparam("registrar", "max_contacts", 10) + +loadmodule "proto_udp.so" + +#### Dialog module +loadmodule "dialog.so" + +#### Exec module +loadmodule "exec.so" + +#### JSON module +loadmodule "json.so" + +#### Diameter module +loadmodule "aaa_diameter.so" +modparam("aaa_diameter", "fd_log_level", 0) +modparam("aaa_diameter", "realm", "diameter.test") +modparam("aaa_diameter", "peer_identity", "server") +modparam("aaa_diameter", "aaa_url", "diameter:/etc/freeDiameter/freeDiameter.conf;extra-avps-file:/etc/opensips/dictionary.opensips") + +####### Routing Logic ######## + +# main request routing logic + +route{ + + if (!mf_process_maxfwd_header(10)) { + send_reply(483,"Too Many Hops"); + exit; + } + + if (has_totag()) { + + # handle hop-by-hop ACK (no routing required) + if ( is_method("ACK") && t_check_trans() ) { + t_relay(); + exit; + } + + # sequential request within a dialog should + # take the path determined by record-routing + if ( !loose_route() ) { + # we do record-routing for all our traffic, so we should not + # receive any sequential requests without Route hdr. + send_reply(404,"Not here"); + exit; + } + + # route it out to whatever destination was set by loose_route() + # in $du (destination URI). + route(relay); + exit; + } + + # CANCEL processing + if (is_method("CANCEL")) { + if (t_check_trans()) + t_relay(); + exit; + } + + # absorb retransmissions, but do not create transaction + t_check_trans(); + + if ( !(is_method("REGISTER") ) ) { + + if (is_myself("$fd")) { + + } else { + # if caller is not local, then called number must be local + + if (!is_myself("$rd")) { + send_reply(403,"Relay Forbidden"); + exit; + } + } + + } + + # preloaded route checking + if (loose_route()) { + xlog("L_ERR", + "Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]"); + if (!is_method("ACK")) + send_reply(403,"Preload Route denied"); + exit; + } + + # record routing + if (!is_method("REGISTER|MESSAGE")) + record_route(); + + # account only INVITEs + if (is_method("INVITE")) { + + create_dialog(); + + # populate dialog variables to use them inside handlers + $dlg_val(setuptime) = $Ts; + $dlg_val(callid) = $ci; + $dlg_val(originhost) = $(ou{uri.host}); + $dlg_val(originrealm) = $fd; + $dlg_val(account) = $fU; + $dlg_val(destination) = $rU; + + # use the unix2ntp helper script to convert unix time to 32bit format + # for the Event-Timestamp AVP + # can this be done directly inside the cfg file? + exec("/usr/share/cgrates/tutorials/osips_diam/etc/opensips/unix2ntp", "$dlg_val(setuptime)", $var(setuptime32bit), , ); + + # Send blocking CCR INITIAL_REQUEST and retrieve MaxUsage + $var(payload) = "[ + { \"Session-Id\": \"" + $dlg_val(callid) + "\" }, + { \"Origin-Host\": \"client.diameter.test\" }, + { \"Origin-Realm\": \"diameter.test\" }, + { \"Destination-Realm\": \"diameter.test\" }, + { \"Service-Context-Id\": \"voice@OsipsDiamCCR\" }, + { \"CC-Request-Type\": 1 }, + { \"CC-Request-Number\": 0 }, + { \"Event-Timestamp\": \"" + $var(setuptime32bit) + "\" }, + { \"User-Name\": \"" + $dlg_val(account) + "\" }, + { \"Subscription-Id\": [ + { \"Subscription-Id-Type\": 0 }, + { \"Subscription-Id-Data\": \"" + $dlg_val(account) + "\" } + ]}, + { \"Requested-Service-Unit\": [ + {\"CC-Time\": 10800} + ]}, + { \"Service-Information\": [ + {\"IMS-Information\": [ + { \"Calling-Party-Address\": \"" + $dlg_val(account) + "\" }, + { \"Called-Party-Address\": \"" + $dlg_val(destination) + "\" } + ]} + ]} + ]"; + + # send the diameter request + dm_send_request(4, 272, $var(payload), $var(rpl_avps)); + + # handle the diameter reply + $json(rpl) := $var(rpl_avps); + if ($rc != 1) { + xlog("L_NOTICE", "failed to send diameter request: rc=$rc\n"); + send_reply(403, "Forbidden"); + exit; + } + + # check Result-Code coming from diameter server + # is there a way to index directly by the name of the key without knowing the index first? + if ($json(rpl[5]/Result-Code) != 2001) { + xlog("L_NOTICE", "failed to authorize session (Result-Code=$json(rpl[5]/Result-Code))\n"); + send_reply(403, "Forbidden"); + exit; + } + + # set dialog timeout based on the received Granted-Service-Unit AVP + $DLG_timeout = $json(rpl[6]/Granted-Service-Unit[0]/CC-Time); + + # dialog handlers + # only send async requests due to issue detailed at the bottom of the cfg file + dlg_on_answer("handle_answer"); + dlg_on_hangup("handle_hangup"); + dlg_on_timeout("handle_timeout"); + } + + if (!is_myself("$rd")) { + append_hf("P-hint: outbound\r\n"); + + route(relay); + } + + # requests for my domain + + if (is_method("PUBLISH|SUBSCRIBE")) { + send_reply(503, "Service Unavailable"); + exit; + } + + if (is_method("REGISTER")) { + # store the registration and generate a SIP reply + if (!save("location")) + xlog("failed to register AoR $tu\n"); + + exit; + } + + if ($rU==NULL) { + # request with no Username in RURI + send_reply(484,"Address Incomplete"); + exit; + } + + # do lookup with method filtering + if (!lookup("location","method-filtering")) { + t_reply(404, "Not Found"); + exit; + } + + route(relay); +} + +route[handle_answer] { + $dlg_val(answertime) = $Ts; + exec("/usr/share/cgrates/tutorials/osips_diam/etc/opensips/unix2ntp", "$dlg_val(answertime)", $dlg_val(answertime32bit), , ); + $var(payload) = "[ + { \"Session-Id\": \"" + $dlg_val(callid) + "\" }, + { \"Origin-Host\": \"client.diameter.test\" }, + { \"Origin-Realm\": \"diameter.test\" }, + { \"Destination-Realm\": \"diameter.test\" }, + { \"Service-Context-Id\": \"voice@OsipsDiamCCR\" }, + { \"CC-Request-Type\": 2 }, + { \"CC-Request-Number\": 1 }, + { \"Event-Timestamp\": \"" + $dlg_val(answertime32bit) + "\" }, + { \"User-Name\": \"" + $dlg_val(account) + "\" }, + { \"Subscription-Id\": [ + { \"Subscription-Id-Type\": 0 }, + { \"Subscription-Id-Data\": \"" + $dlg_val(account) + "\" } + ]}, + { \"Requested-Service-Unit\": [ + {\"CC-Time\": 0} + ]}, + { \"Service-Information\": [ + {\"IMS-Information\": [ + { \"Calling-Party-Address\": \"" + $dlg_val(account) + "\" }, + { \"Called-Party-Address\": \"" + $dlg_val(destination) + "\" } + ]} + ]} + ]"; + + async(dm_send_request(4, 272, $var(payload), $var(rpl_avps)), dm_reply); + +} + +route[handle_hangup] { + $var(duration1) = $Ts - $dlg_val(answertime); + $var(payload) = "[ + { \"Session-Id\": \"" + $dlg_val(callid) + "\" }, + { \"Origin-Host\": \"client.diameter.test\" }, + { \"Origin-Realm\": \"diameter.test\" }, + { \"Destination-Realm\": \"diameter.test\" }, + { \"Service-Context-Id\": \"voice@OsipsDiamCCR\" }, + { \"CC-Request-Type\": 3 }, + { \"CC-Request-Number\": 2 }, + { \"Termination-Cause\": 1 }, + { \"Event-Timestamp\": \"" + $dlg_val(answertime32bit) + "\" }, + { \"User-Name\": \"" + $dlg_val(account) + "\" }, + { \"Subscription-Id\": [ + { \"Subscription-Id-Type\": 0 }, + { \"Subscription-Id-Data\": \"" + $dlg_val(account) + "\" } + ]}, + { \"Used-Service-Unit\": [ + {\"CC-Time\": " + $var(duration1) + " } + ]}, + { \"Service-Information\": [ + {\"IMS-Information\": [ + { \"Calling-Party-Address\": \"" + $dlg_val(account) + "\" }, + { \"Called-Party-Address\": \"" + $dlg_val(destination) + "\" } + ]} + ]} + ]"; + + async(dm_send_request(4, 272, $var(payload), $var(rpl_avps)), dm_reply); +} + +# should be almost identical to handle_hangup. Sends Termination-Cause 6 (DIAMETER_AUTH_EXPIRED) instead of 1 (DIAMETER_LOGOUT) +route[handle_timeout] { + $var(duration2) = $Ts - $dlg_val(answertime); + $var(payload) = "[ + { \"Session-Id\": \"" + $dlg_val(callid) + "\" }, + { \"Origin-Host\": \"client.diameter.test\" }, + { \"Origin-Realm\": \"diameter.test\" }, + { \"Destination-Realm\": \"diameter.test\" }, + { \"Service-Context-Id\": \"voice@OsipsDiamCCR\" }, + { \"CC-Request-Type\": 3 }, + { \"CC-Request-Number\": 2 }, + { \"Termination-Cause\": 6 }, + { \"Event-Timestamp\": \"" + $dlg_val(answertime32bit) + "\" }, + { \"User-Name\": \"" + $dlg_val(account) + "\" }, + { \"Subscription-Id\": [ + { \"Subscription-Id-Type\": 0 }, + { \"Subscription-Id-Data\": \"" + $dlg_val(account) + "\" } + ]}, + { \"Used-Service-Unit\": [ + {\"CC-Time\": " + $var(duration2) + " } + ]}, + { \"Service-Information\": [ + {\"IMS-Information\": [ + { \"Calling-Party-Address\": \"" + $dlg_val(account) + "\" }, + { \"Called-Party-Address\": \"" + $dlg_val(destination) + "\" } + ]} + ]} + ]"; + + async(dm_send_request(4, 272, $var(payload), $var(rpl_avps)), dm_reply); +} + +# async dm reply handler +route[dm_reply] { + $json(rpl) := $var(rpl_avps); + if ($rc != 1 || $json(rpl[5]/Result-Code) != 2001) { + xlog("L_NOTICE", "rc=$rc\nResult-Code=$json(rpl[5]/Result-Code)\nCC-Request-Type=$json(rpl[3]/CC-Request-Type)\n"); + exit; + } +} + +route[relay] { + # for INVITEs enable some additional helper routes + if (is_method("INVITE")) { + t_on_branch("per_branch_ops"); + t_on_reply("handle_nat"); + t_on_failure("missed_call"); + } + + if (!t_relay()) { + send_reply(500,"Internal Error"); + } + exit; +} + +branch_route[per_branch_ops] { + xlog("new branch at $ru\n"); +} + +onreply_route[handle_nat] { + xlog("incoming reply\n"); +} + +failure_route[missed_call] { + if (t_was_cancelled()) { + exit; + } + + # uncomment the following lines if you want to block client + # redirect based on 3xx replies. + ##if (t_check_status("3[0-9][0-9]")) { + ##t_reply(404,"Not found"); + ## exit; + ##} +} + + + +# Tried to add Time-Stamps as an extra field to keep track of SetupTime/AnswerTime and set Event-Timestamp to the time of the event itself. Then the route started to behave weirdly. It was sending only the authorize diameter request and then, when the call was supposed to hangup, it sent the authorize request again. +# +# { \"Time-Stamps\": [ +# {\"SIP-Request-Timestamp\": " + $dlg_val(setuptime) + " }, +# {\"SIP-Response-Timestamp\": \"" + $dlg_val(answertime32bit) + "\" } +# ]} + +# Had to use async diameter requests, because otherwise it would not do anything on hangup/timeout. It would be stuck in an infinite loop with these (still has them, but it works because it's async): +# +# Apr 30 21:37:15 [26704] DBG:tm:timer_routine: timer routine:3,tl=0x7fd0bb345400 next=(nil), timeout=125 +# Apr 30 21:37:15 [26704] DBG:tm:delete_handler: removing 0x7fd0bb345350 +# Apr 30 21:37:15 [26704] DBG:tm:delete_cell: delete_cell 0x7fd0bb345350: can't delete -- still reffed (-1) +# Apr 30 21:37:15 [26704] DBG:tm:set_timer: relative timeout is 2 +# Apr 30 21:37:15 [26704] DBG:tm:insert_timer_unsafe: [3]: 0x7fd0bb345400 (127) +# Apr 30 21:37:15 [26704] DBG:tm:delete_handler: done +# Apr 30 21:37:18 [26704] DBG:tm:timer_routine: timer routine:3,tl=0x7fd0bb345400 next=(nil), timeout=127 +# Apr 30 21:37:18 [26704] DBG:tm:delete_handler: removing 0x7fd0bb345350 +# Apr 30 21:37:18 [26704] DBG:tm:delete_cell: delete_cell 0x7fd0bb345350: can't delete -- still reffed (-1) +# Apr 30 21:37:18 [26704] DBG:tm:set_timer: relative timeout is 2 +# Apr 30 21:37:18 [26704] DBG:tm:insert_timer_unsafe: [3]: 0x7fd0bb345400 (129) +# Apr 30 21:37:18 [26704] DBG:tm:delete_handler: done +# Apr 30 21:37:20 [26704] DBG:tm:timer_routine: timer routine:3,tl=0x7fd0bb345400 next=(nil), timeout=129 +# Apr 30 21:37:20 [26704] DBG:tm:delete_handler: removing 0x7fd0bb345350 +# Apr 30 21:37:20 [26704] DBG:tm:delete_cell: delete_cell 0x7fd0bb345350: can't delete -- still reffed (-1) +# Apr 30 21:37:20 [26704] DBG:tm:set_timer: relative timeout is 2 +# Apr 30 21:37:20 [26704] DBG:tm:insert_timer_unsafe: [3]: 0x7fd0bb345400 (131) +# Apr 30 21:37:20 [26704] DBG:tm:delete_handler: done diff --git a/data/tutorials/osips_diam/etc/opensips/unix2ntp b/data/tutorials/osips_diam/etc/opensips/unix2ntp new file mode 100755 index 000000000..c126bf7ad --- /dev/null +++ b/data/tutorials/osips_diam/etc/opensips/unix2ntp @@ -0,0 +1,10 @@ +#!/bin/bash + +# To be used inside opensips.cfg to convert unix time +# to 32bit before sending it inside a diameter request +# payload as Event-Timestamp. + +read unix_time +ntp_offset=2208988800 +ntp_time=$((unix_time + ntp_offset)) +printf "%08x" "$ntp_time" | xxd -r -p diff --git a/data/tutorials/osips_diam/tp/AccountActions.csv b/data/tutorials/osips_diam/tp/AccountActions.csv new file mode 100644 index 000000000..7a2e397b5 --- /dev/null +++ b/data/tutorials/osips_diam/tp/AccountActions.csv @@ -0,0 +1,2 @@ +#Tenant,Account,ActionPlanId,ActionTriggersId,AllowNegative,Disabled +cgrates.org,1001,AP_PACKAGE_1001,,, \ No newline at end of file diff --git a/data/tutorials/osips_diam/tp/ActionPlans.csv b/data/tutorials/osips_diam/tp/ActionPlans.csv new file mode 100644 index 000000000..a459beccf --- /dev/null +++ b/data/tutorials/osips_diam/tp/ActionPlans.csv @@ -0,0 +1,2 @@ +#Id,ActionsId,TimingId,Weight +AP_PACKAGE_1001,ACT_TOPUP_RST_10,*asap,10 \ No newline at end of file diff --git a/data/tutorials/osips_diam/tp/Actions.csv b/data/tutorials/osips_diam/tp/Actions.csv new file mode 100644 index 000000000..7e42ba03c --- /dev/null +++ b/data/tutorials/osips_diam/tp/Actions.csv @@ -0,0 +1,2 @@ +#ActionsId[0],Action[1],ExtraParameters[2],Filter[3],BalanceId[4],BalanceType[5],Categories[6],DestinationIds[7],RatingSubject[8],SharedGroup[9],ExpiryTime[10],TimingIds[11],Units[12],BalanceWeight[13],BalanceBlocker[14],BalanceDisabled[15],Weight[16] +ACT_TOPUP_RST_10,*topup_reset,,,balance_test,*monetary,,*any,,,*unlimited,,10,10,false,false,10 \ No newline at end of file diff --git a/data/tutorials/osips_diam/tp/Chargers.csv b/data/tutorials/osips_diam/tp/Chargers.csv new file mode 100644 index 000000000..c270b5867 --- /dev/null +++ b/data/tutorials/osips_diam/tp/Chargers.csv @@ -0,0 +1,2 @@ +#Tenant,ID,FilterIDs,ActivationInterval,RunID,AttributeIDs,Weight +cgrates.org,DEFAULT,,,*default,*none,0 \ No newline at end of file diff --git a/data/tutorials/osips_diam/tp/DestinationRates.csv b/data/tutorials/osips_diam/tp/DestinationRates.csv new file mode 100644 index 000000000..20a165ad6 --- /dev/null +++ b/data/tutorials/osips_diam/tp/DestinationRates.csv @@ -0,0 +1,2 @@ +#Id,DestinationId,RatesTag,RoundingMethod,RoundingDecimals,MaxCost,MaxCostStrategy +DR_1002,DST_1002,RT_20CNT,*up,4,0, \ No newline at end of file diff --git a/data/tutorials/osips_diam/tp/Destinations.csv b/data/tutorials/osips_diam/tp/Destinations.csv new file mode 100644 index 000000000..dd82a1bfe --- /dev/null +++ b/data/tutorials/osips_diam/tp/Destinations.csv @@ -0,0 +1,2 @@ +#Id,Prefix +DST_1002,1002 \ No newline at end of file diff --git a/data/tutorials/osips_diam/tp/Rates.csv b/data/tutorials/osips_diam/tp/Rates.csv new file mode 100644 index 000000000..6c0f66b40 --- /dev/null +++ b/data/tutorials/osips_diam/tp/Rates.csv @@ -0,0 +1,3 @@ +#Id,ConnectFee,Rate,RateUnit,RateIncrement,GroupIntervalStart +RT_20CNT,0.4,0.2,60s,60s,0s +RT_20CNT,0,0.1,60s,1s,60s \ No newline at end of file diff --git a/data/tutorials/osips_diam/tp/RatingPlans.csv b/data/tutorials/osips_diam/tp/RatingPlans.csv new file mode 100644 index 000000000..23982c801 --- /dev/null +++ b/data/tutorials/osips_diam/tp/RatingPlans.csv @@ -0,0 +1,2 @@ +#Id,DestinationRatesId,TimingTag,Weight +RP_1001,DR_1002,*any,10 \ No newline at end of file diff --git a/data/tutorials/osips_diam/tp/RatingProfiles.csv b/data/tutorials/osips_diam/tp/RatingProfiles.csv new file mode 100644 index 000000000..460618a59 --- /dev/null +++ b/data/tutorials/osips_diam/tp/RatingProfiles.csv @@ -0,0 +1,2 @@ +#Tenant,Category,Subject,ActivationTime,RatingPlanId,RatesFallbackSubject +cgrates.org,call,1001,2014-01-14T00:00:00Z,RP_1001, \ No newline at end of file