| 1 | |
|---|
| 2 | Prepaid functionality is achieved by: |
|---|
| 3 | |
|---|
| 4 | 1. An external session control module, not provided by CDRTool, that |
|---|
| 5 | maintains the session status and terminates the sessions by sending BYE |
|---|
| 6 | messages to both SIP end-points. The session control module communicates |
|---|
| 7 | with both the SIP Proxy and CDRTool using the API described below. |
|---|
| 8 | |
|---|
| 9 | 2. CDRTool rating engine - provides session management, calculates maximum |
|---|
| 10 | session time based on user balance and destination using the same algorithm |
|---|
| 11 | as the postpaid engine and debits the subscriber balance. |
|---|
| 12 | |
|---|
| 13 | See doc/PrepaidEngine.pdf for a detailed session flow. |
|---|
| 14 | |
|---|
| 15 | |
|---|
| 16 | Prepaid API |
|---|
| 17 | ----------- |
|---|
| 18 | |
|---|
| 19 | CDRTool prepaid engine is used to compute session times based on user |
|---|
| 20 | balance and destination dependent rates and debit the balance when the |
|---|
| 21 | session has ended. The prepaid engine is accessible over the TCP socket |
|---|
| 22 | where the rating engine listens. |
|---|
| 23 | |
|---|
| 24 | The TCP socket location is configured in /etc/default/cdrtool. The |
|---|
| 25 | connection can be tested manually using the telnet command and by typing |
|---|
| 26 | help at the prompt: |
|---|
| 27 | |
|---|
| 28 | tstuser@host:~$ telnet 10.0.0.0.1 9024 |
|---|
| 29 | Trying 10.0.0.1... |
|---|
| 30 | Connected to 10.0.0.1. |
|---|
| 31 | Escape character is '^]'. |
|---|
| 32 | Help |
|---|
| 33 | MaxSessionTime CallId=6432622xvv@1 From=sip:123@example.com To=sip:0031650222333@example.com Duration=7200 Gateway=10.0.0.1 Lock=1 |
|---|
| 34 | ShowPrice From=sip:123@example.com To=sip:0031650222333@example.com Gateway=10.0.0.1 Duration=59 |
|---|
| 35 | DebitBalance CallId=6432622xvv@1 From=sip:123@example.com To=sip:0031650222333@example.com Gateway=10.0.0.1 Duration=59 |
|---|
| 36 | AddBalance From=123@example.com Value=10.00 |
|---|
| 37 | GetBalance From=123@example.com |
|---|
| 38 | GetBalanceHistory From=123@example.com |
|---|
| 39 | DeleteBalance From=123@example.com |
|---|
| 40 | DeleteBalanceHistory From=123@example.com |
|---|
| 41 | |
|---|
| 42 | In the example above only the commands related to the prepaid functionality |
|---|
| 43 | are highlighted. |
|---|
| 44 | |
|---|
| 45 | The session control module must send commands based on the syntax described |
|---|
| 46 | below, to the CDRTool prepaid engine. A command is a line of text containing |
|---|
| 47 | a keyword, which is the command itself followed by a number of parameters |
|---|
| 48 | separated by space. The command ends with an enter in unix style: '\n' For |
|---|
| 49 | each such request there will be a reply on one or more lines, containing |
|---|
| 50 | information that depends on the command that was sent. A reply ends when a |
|---|
| 51 | double end of line (\n) is received: \n\n (this is needed because there are |
|---|
| 52 | commands that require multiline responses). How the information contained in |
|---|
| 53 | a reply is to be interpreted depends on the command itself and is described |
|---|
| 54 | below. |
|---|
| 55 | |
|---|
| 56 | |
|---|
| 57 | 1. Asking for the session time limit |
|---|
| 58 | ------------------------------ |
|---|
| 59 | |
|---|
| 60 | To request the maximum time for a new session, the following request must be |
|---|
| 61 | sent to CDRTool rating engine on one line: |
|---|
| 62 | |
|---|
| 63 | MaxSessionTime CallId=6432622xvv@1 From=sip:123@example.com |
|---|
| 64 | To=sip:0031650222@example.com Duration=7200 Gateway=81.20.68.10 |
|---|
| 65 | ENUMtl=tld.com\n |
|---|
| 66 | |
|---|
| 67 | MaxSessionTime request is created when a new INVITE arrives at the SIP |
|---|
| 68 | Proxy/session control application. Depending on the session setup time, the |
|---|
| 69 | maximum session time might not be accurate because the session setup time is |
|---|
| 70 | unknown at the INVITE stage. You can call MaxSessionTime again when the |
|---|
| 71 | session has been setup (the OK has been received), in which case the rating |
|---|
| 72 | engine will provide the updated maximum session time for the session that |
|---|
| 73 | was in progress. |
|---|
| 74 | |
|---|
| 75 | Duration is a limit imposed to the time allowed, in case you want to limit |
|---|
| 76 | the session time to a maximum duration. This will be a top limit for the |
|---|
| 77 | session's time, even if the user could talk more than that based on his |
|---|
| 78 | current balance. The session control module may limit all sessions to 7200 |
|---|
| 79 | seconds for example. |
|---|
| 80 | |
|---|
| 81 | To may actually be sip:number@domain;param=value so you must extract the |
|---|
| 82 | username part which is the number From is a sip URI which can be "full |
|---|
| 83 | name". |
|---|
| 84 | |
|---|
| 85 | ENUMtld parameter is optional but if present discounts based on ENUM can be |
|---|
| 86 | applied (see RATING.txt for more information on ENUM discounts). |
|---|
| 87 | |
|---|
| 88 | Call control expects a reply like below: |
|---|
| 89 | |
|---|
| 90 | value\n\n |
|---|
| 91 | |
|---|
| 92 | value is the numeric time in seconds, or None, None meaning it's not a |
|---|
| 93 | prepaid account and it doesn't have any limitation, or it's a free |
|---|
| 94 | destination, like a 0800 number. |
|---|
| 95 | |
|---|
| 96 | To field must be the canonical destination SIP URI after all possible |
|---|
| 97 | lookups in the SIP Proxy. |
|---|
| 98 | |
|---|
| 99 | 2. Debiting the balance when the conversation ends |
|---|
| 100 | ----------------------------------------------- |
|---|
| 101 | |
|---|
| 102 | To debit money from the user balance when the conversation ends, the session |
|---|
| 103 | control module must send the following command on one line: |
|---|
| 104 | |
|---|
| 105 | DebitBalance CallId=6432622xvv@1 From=sip:123@example.com |
|---|
| 106 | To=sip:0031650222@example.com Gateway=81.20.68.10 Duration=59\n |
|---|
| 107 | |
|---|
| 108 | Same notes about the From and To fields as above apply. The call id must |
|---|
| 109 | match the call id provided to the correspondent MaxSessionTime command. |
|---|
| 110 | |
|---|
| 111 | and expects in return: |
|---|
| 112 | |
|---|
| 113 | value |
|---|
| 114 | maxsessiontime\n\n |
|---|
| 115 | |
|---|
| 116 | where value is one of: OK (success), Failed (failure), Not Prepaid (account |
|---|
| 117 | is not prepaid - this case is not considered a failure). |
|---|
| 118 | |
|---|
| 119 | The second line contains the new maximum session time that is valid after |
|---|
| 120 | debiting the balance for this session. It can be used to update the maximum |
|---|
| 121 | session time of other sessions that are already in progress for the same |
|---|
| 122 | user. |
|---|
| 123 | |
|---|
| 124 | Note that once your have called once MaxSessiontime, you must also call the |
|---|
| 125 | correspondent DebitBalance even if the session has zero seconds duration. If |
|---|
| 126 | you do not do this, the session will stay in progress for the maximum |
|---|
| 127 | session time and any subsequent session setup will have its maximum duration |
|---|
| 128 | altered by this existing session in progress. Sessions in progress will be |
|---|
| 129 | automatically purged after they expire when a new MaxSessionTime request for |
|---|
| 130 | the same user is received. |
|---|
| 131 | |
|---|
| 132 | The debited value and the new balance is recorded in the prepaid_history |
|---|
| 133 | table. |
|---|
| 134 | |
|---|
| 135 | If the session does not exist, no balance will be debited. To force the |
|---|
| 136 | DebitBalance to debit the balance even if no session in progress exists, you |
|---|
| 137 | can supply Force=1 parameter. This can be useful if for some reason the |
|---|
| 138 | requests have been lost due to network problems. In such case the requests |
|---|
| 139 | can be applied at a later time. |
|---|
| 140 | |
|---|
| 141 | |
|---|
| 142 | Prepaid ongoing sessions |
|---|
| 143 | ------------------------ |
|---|
| 144 | |
|---|
| 145 | To see the sessions in progress go to Rating -> Prepaid. |
|---|
| 146 | |
|---|
| 147 | Fill in the Sesssions field: >0 and click Search. |
|---|
| 148 | |
|---|
| 149 | |
|---|
| 150 | Third party tools |
|---|
| 151 | ----------------- |
|---|
| 152 | |
|---|
| 153 | FreeRADIUS-CDRTool by Dan-Cristian Bogos |
|---|
| 154 | |
|---|
| 155 | FreeRADIUS module providing connectivity to CDRTool prepaid engine. Provides |
|---|
| 156 | prepaid authentication for sessions proxied by OpenSER and returns to OpenSER |
|---|
| 157 | MaxCallDuration and user Credit. Can be easily extended to support other SIP |
|---|
| 158 | or H323 devices. |
|---|
| 159 | |
|---|
| 160 | http://sourceforge.net/projects/frad-cdrtool |
|---|
| 161 | |
|---|
| 162 | |
|---|
| 163 | Notes |
|---|
| 164 | ----- |
|---|
| 165 | |
|---|
| 166 | The balance is debited correctly regardless of the moment of CDR |
|---|
| 167 | normalization. Renormalization of sessions do not recalculate balance. |
|---|
| 168 | |
|---|
| 169 | |
|---|
| 170 | Simultaneous prepaid sessions |
|---|
| 171 | ----------------------------- |
|---|
| 172 | |
|---|
| 173 | Since version 6.6.0 is possible to have multiple sessions using the same |
|---|
| 174 | prepaid account without the risk of reaching a negative balance. |
|---|
| 175 | |
|---|
| 176 | The modus operandi is based on a simple model: all prepaid sessions |
|---|
| 177 | belonging to the same account must end at the same moment regadless of the |
|---|
| 178 | called destinations and their different prices. The rating engine simply |
|---|
| 179 | calculates that moment based on the data available from ongoing sessions, |
|---|
| 180 | available balance and the new session setup request. This scheme provides a |
|---|
| 181 | fair balancing policy with small performance penalty on the servers. |
|---|
| 182 | |
|---|
| 183 | By using this simple model there is no need to poll any database during |
|---|
| 184 | ongoing sessions, which could become major scalability problem. All the work |
|---|
| 185 | is done only once at session setup time. This will increase the load on the |
|---|
| 186 | rating engine only during session setup and only for the users that have |
|---|
| 187 | parallel sessions, the rating engine will perform internally during each |
|---|
| 188 | session setup 2 x the number of sessions sessions to ShowPrice and |
|---|
| 189 | MaxSessionTime functions. To be entirely accurate the session control must |
|---|
| 190 | also connect back to the rating engine when the session has been setup so |
|---|
| 191 | that the session expiration can be updated. |
|---|
| 192 | |
|---|
| 193 | You must set $RatingEngine['prepaid_lock'] to 0 in etc/cdrtool/global.inc |
|---|
| 194 | and update the session control software module that keeps track of the |
|---|
| 195 | maximum session time to enforce session ending for all sessions belonging to |
|---|
| 196 | the same sip account at the same time based on the last value of |
|---|
| 197 | MaxSessionTime received from the rating engine. The previous version |
|---|
| 198 | required the session control module to keep the time counter per sip |
|---|
| 199 | session, now it needs to sync the counter for all sessions belonging to the |
|---|
| 200 | same sip account. See below the pseudo code for the session control engine. |
|---|
| 201 | |
|---|
| 202 | The list of ongoing prepaid sessions can be monitored in the Prepaid |
|---|
| 203 | accounts section of the Rating pages. For each sip account that has ongoing |
|---|
| 204 | sessions a link marked Sessions apears in the left side of the prepaid |
|---|
| 205 | record. Click on it to display the ongoing prepaid sessions. The 'Delete |
|---|
| 206 | session' button allows the removal of the session form the rating engine. |
|---|
| 207 | The expired sessions that have not been closed properly by the session |
|---|
| 208 | control module are automatically purged 120 seconds after expiration, which |
|---|
| 209 | has been chosen to cover a reasonable session setup time. |
|---|
| 210 | |
|---|
| 211 | The time and balance distribution between parallel sessions is logged |
|---|
| 212 | detailed in the syslog: |
|---|
| 213 | |
|---|
| 214 | cdrtool[13292]: MaxSessionTime From=sip:adi@umts.ro Lock=1 To=sip:00318008185@umts.ro CallId=waaivgq89aTxmPIG6JIsPhODUe0tRVf- Duration=36000 Gateway=80.101.96.20 |
|---|
| 215 | cdrtool[13292]: ConnectFee=0.0450 Span=1 Duration=16 DestId=31646 default Profile=442 Period=weekend Rate=442 Interval=0-24 Cost=0.1600/60 Price=0.0427 |
|---|
| 216 | cdrtool[13292]: Ongoing prepaid session uw1znivnqoeofyezzuiiisudukw64cm- for adi@umts.ro to sip:0031646999425@umts.ro: duration=16, price=0.0877 |
|---|
| 217 | cdrtool[13292]: Balance for adi@umts.ro having 1 ongoing sessions: database=9.9534, due=0.0877, real=9.8657 |
|---|
| 218 | cdrtool[13292]: Maximum duration for adi@umts.ro to destination 31646 having balance=9.8657 is 3700 |
|---|
| 219 | cdrtool[13292]: Maximum duration for adi@umts.ro to destination 31800 having balance=9.8657 is 29597 |
|---|
| 220 | cdrtool[13292]: Maximum duration agregated for adi@umts.ro is (Balance=9.8657)/(Sum of price per second for each destination=0.0030)=3288 s |
|---|
| 221 | cdrtool[13292]: CallId=waaivgq89atxmpig6jisphodue0trvf- BillingParty=adi@umts.ro DestId=31800 Balance=9.8657 MaxSessionTime=3288 Spans=2 |
|---|
| 222 | |
|---|
| 223 | |
|---|
| 224 | Pseudo-code for rating engine |
|---|
| 225 | ----------------------------- |
|---|
| 226 | |
|---|
| 227 | This code is actually implemented in cdrtool. |
|---|
| 228 | |
|---|
| 229 | max_session_time (session, from, to) { |
|---|
| 230 | list(balance,active_sessions)=get_prepaid_account_from_database(); |
|---|
| 231 | |
|---|
| 232 | if (count(active_sessions[from]) > 0) { |
|---|
| 233 | if (purge_expired_sessions()) { |
|---|
| 234 | // reload prepaid data |
|---|
| 235 | list(balance,active_sessions)=get_prepaid_account_from_database(); |
|---|
| 236 | } |
|---|
| 237 | |
|---|
| 238 | max=calculate_aggregated_maxsession_time(active_sessions[from],balance) |
|---|
| 239 | return max |
|---|
| 240 | |
|---|
| 241 | } else { |
|---|
| 242 | // is the first session |
|---|
| 243 | rate = new Rate(session) |
|---|
| 244 | active_sessions[from][]=session |
|---|
| 245 | return rate->max_session_time() |
|---|
| 246 | } |
|---|
| 247 | } |
|---|
| 248 | |
|---|
| 249 | calculate_aggregated_maxsession_time(active_sessions,balance) { |
|---|
| 250 | // simulate session termination for all ongoing sessions |
|---|
| 251 | foreach (session in active_sessions[from]) { |
|---|
| 252 | due_balance=used_balance+show_price(session) |
|---|
| 253 | } |
|---|
| 254 | |
|---|
| 255 | // calculate new virtual balance |
|---|
| 256 | balance_new=balance-due_balance |
|---|
| 257 | |
|---|
| 258 | // simulate we start parallel sessions from scratch at this moment |
|---|
| 259 | // including the new sessionn setup request |
|---|
| 260 | // we skip any connect cost for session already in progress |
|---|
| 261 | active_sessions_new[from]=active_sessions[from] |
|---|
| 262 | active_sessions_new[from][]=session |
|---|
| 263 | foreach (session in active_sessions_new[from]) { |
|---|
| 264 | rate = new Rate(session) |
|---|
| 265 | maxsessiontime=rate->max_session_time() |
|---|
| 266 | // calculate price per second for each session |
|---|
| 267 | price_per_second=price_per_second+balance_new/maxsessiontime |
|---|
| 268 | } |
|---|
| 269 | |
|---|
| 270 | return balance/price_per_second |
|---|
| 271 | } |
|---|
| 272 | |
|---|
| 273 | debit_balance(session, from, to, duration) { |
|---|
| 274 | list(balance,active_sessions)=get_prepaid_account_from_database(); |
|---|
| 275 | foreach (session in active_sessions[from]) { |
|---|
| 276 | if (session == session) continue |
|---|
| 277 | new_active_sessions[] = session |
|---|
| 278 | } |
|---|
| 279 | // update list of active sessions |
|---|
| 280 | active_sessions[from]=new_active_sessions |
|---|
| 281 | price=showprice(session,from,to,duration) |
|---|
| 282 | balance=balance-price |
|---|
| 283 | update_prepaid_account_to_database(balance,active_sessions); |
|---|
| 284 | if (count(active_sessions[from]) > 0) { |
|---|
| 285 | max=calculate_aggregated_maxsession_time() |
|---|
| 286 | } else { |
|---|
| 287 | max=0 |
|---|
| 288 | } |
|---|
| 289 | return max |
|---|
| 290 | } |
|---|
| 291 | |
|---|
| 292 | |
|---|
| 293 | Pseudo-code for session control engine |
|---|
| 294 | -------------------------------------- |
|---|
| 295 | |
|---|
| 296 | on_session_setup () { |
|---|
| 297 | max=cdrtool_max_session_time(session, from, to) |
|---|
| 298 | if (sip_connect_session() == true) { |
|---|
| 299 | // add session to the list of active sessions |
|---|
| 300 | // update tiomer again becuase we had a variable setup time |
|---|
| 301 | max=cdrtool_max_session_time(session, from, to) |
|---|
| 302 | active_sessions[from][]=session |
|---|
| 303 | // set maxsession time for all remaining sessions of the same user |
|---|
| 304 | foreach (session in active_sessions[from]) { |
|---|
| 305 | session.maxsessiontime = max |
|---|
| 306 | } |
|---|
| 307 | } |
|---|
| 308 | } |
|---|
| 309 | |
|---|
| 310 | on_session_disconnect () { |
|---|
| 311 | sip_disconnect_session(); |
|---|
| 312 | max=cdrtool_debit_balance(session, from, to, duration) |
|---|
| 313 | // remove session from the list of active sessions |
|---|
| 314 | foreach (session in active_sessions[from]) { |
|---|
| 315 | if (session == session) continue |
|---|
| 316 | new_active_sessions[] = session |
|---|
| 317 | } |
|---|
| 318 | // update list of active sessions |
|---|
| 319 | active_sessions[from]=new_active_sessions |
|---|
| 320 | foreach (session in active_sessions[from]) { |
|---|
| 321 | session.maxsessiontime = max |
|---|
| 322 | } |
|---|
| 323 | } |
|---|