通过evapi模块在对话开始和对话结束时发送cdr信息
kamailio.cfg
#!KAMAILIO
# Sample demo config for Kamailio-CGRateS communication
# tested against Kamailio 5.1
####### 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
listen=udp:enp0s3:5060
listen=udp:127.0.0.1:5080
listen=udp:127.0.0.1:5060
listen=udp:enp0s3:5080
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 "jsonrpcs.so"
# ----------------- setting module-specific parameters ---------------
modparam("evapi", "bind_addr", "127.0.0.1:8448")
# ----- 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", "cgrconn=>size=1;")
####### Routing Logic ########
include_file "kamailio-cgrates.cfg"
# 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();
# 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
# SIMPLE AUTH methods
if is_method("REGISTER|SUBSCRIBE|PUBLISH") {
route(CGRATES_SIMPLEAUTH_REQUEST);
exit;
}
if ($rU==$null) {
# request with no Username in RURI
sl_send_reply("484","Address Incomplete");
exit;
}
if !is_method("INVITE") {
sl_send_reply("503", "Unsupported method");
exit;
}
route(CGRATES_SESSIONAUTH_REQUEST);
exit;
}
# Here will land requests after processing them with CGRateS.
route[CGRATES_SIMPLEAUTH_REPLY] {
if $var(cgrError) != "" {
xlog("CGR_PROFILE_ERROR: $var(cgrError)");
sl_send_reply("503","CGR_ERROR");
exit;
}
# parse the CGRateS attributes
route(PARSE_CGRATES_ATTRIBUTES);
# password based auth
route(AUTH);
if is_method("REGISTER") {
route(REGISTRAR);
exit;
}
# other methods going over location
route(LOCATION);
route(RELAY);
}
# Here will land requests after processing them with CGRateS. Call RELAY or other routes following this route
route[CGRATES_SESSIONAUTH_REPLY] {
if $var(cgrError) != "" {
xlog("CGR_AUTH_ERROR: $var(cgrError)");
sl_send_reply("503","CGR_ERROR");
exit;
}
# parse the CGRateS attributes
route(PARSE_CGRATES_ATTRIBUTES);
# password based auth
route(AUTH);
# track the dialog so we can set it's timeout and channel variables to store in CDRs
# cannot initialize the dialog sooner due to AUTH
dlg_manage();
$dlg_var(SetupTime) = $TS;
$dlg_var(cgrOriginID) = $dlg(callid) + ";" + $dlg(from_tag);
$dlg_var(cgrTenant) = "cgrates.org";
$dlg_var(cgrReqType) = $avp(RequestType);
$dlg_var(cgrAccount) = $fU;
$dlg_var(cgrDestination) = $rU;
$dlg_var(paypalAccount) = $avp(PaypalAccount);
if $var(cgrMaxUsage) == 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(cgrMaxUsage)") {
sl_send_reply("503","CGR_MAX_USAGE_ERROR");
exit;
}
if $var(cgrRoutes) != "" { # Enforce the route variable to the first one received from CGRateS, here more for demo purposes
$dlg_var(cgrRoutes) = $(var(cgrRoutes){s.select,0,,});
}
# user location service
route(LOCATION);
# send out
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(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", "$avp(Password)", "0") ) {
www_challenge("$td", "0");
exit;
}
} else { # All other methods here
if ( strempty($au) || !pv_proxy_authenticate("$td", "$avp(Password)", "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;
}
}
kamailio-cgrates.cfg
# 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);
}
# Parse the CGRateS attributes from encoded variable into pseudovariables
route[PARSE_CGRATES_ATTRIBUTES] {
# convert encoded attributes into individual Kamailio pseudovariables
$var(idx) = 0;
while !strempty($(var(cgrAttributes){s.select,$var(idx),,})) {
$avp($(var(cgrAttributes){s.select,$var(idx),,}{s.select,0,:}))
= $(var(cgrAttributes){s.select,$var(idx),,}{s.select,1,:});
$var(idx) = $var(idx) + 1;
}
}
# 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,"})]}');
}
route[CGR_DLG_LIST] {
if $sht(cgrconn=>cgr) == $null {
sl_send_reply("503","Charging controller unreachable");
exit;
}
jsonrpc_exec('{"jsonrpc":"2.0","id":1, "method":"dlg.list","params":[]}');
evapi_relay("{\"event\":\"CGR_DLG_LIST_REPLY\",
\"jsonrpl_body\":$jsonrpl(body)}");
}
# Route to mainly query account password from CGRateS
route[CGRATES_SIMPLEAUTH_REQUEST] {
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_flags\":\"*attributes\",
\"cgr_context\":\"simpleauth\",
\"reply_route\":\"CGR_SIMPLEAUTH_REPLY\",
\"Account\":\"$fU\"}");
}
# Route receiving simpleauth replies, sanitizes them and dispatch back into Kamailio inside CGRATES_SIMPLEAUTH_REPLY
route[CGR_SIMPLEAUTH_REPLY] {
json_get_field("$evapi(msg)", "TransactionIndex", "$var(TransactionIndex)");
$var(TransactionIndex) = $(var(TransactionIndex){s.rm,"});
$var(id_index) = $(var(TransactionIndex){s.int});
json_get_field("$evapi(msg)", "TransactionLabel", "$var(TransactionLabel)");
$var(TransactionLabel) = $(var(TransactionLabel){s.rm,"});
$var(id_label) = $(var(TransactionLabel){s.int});
json_get_field("$evapi(msg)", "Attributes", "$var(cgrAttributes)");
$var(cgrAttributes) = $(var(cgrAttributes){s.rm,"});
json_get_field("$evapi(msg)", "Error", "$var(cgrError)");
$var(cgrError) = $(var(cgrError){s.rm,"});
t_continue("$var(id_index)", "$var(id_label)", "CGRATES_SIMPLEAUTH_REPLY"); # Unpark the transaction
}
# Request session auth information from CGRateS
route[CGRATES_SESSIONAUTH_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_flags\":\"*attributes;*accounts;*routes;*resources;*thresholds\",
\"reply_route\":\"CGR_SESSIONAUTH_REPLY\",
\"Account\":\"$fU\",
\"Destination\":\"$rU\",
\"SetupTime\":\"$TS\"}");
}
# Process SESSIONAUTH_reply from CGRateS
route[CGR_SESSIONAUTH_REPLY] {
json_get_field("$evapi(msg)", "TransactionIndex", "$var(TransactionIndex)");
$var(TransactionIndex) = $(var(TransactionIndex){s.rm,"});
$var(id_index) = $(var(TransactionIndex){s.int});
json_get_field("$evapi(msg)", "TransactionLabel", "$var(TransactionLabel)");
$var(TransactionLabel) = $(var(TransactionLabel){s.rm,"});
$var(id_label) = $(var(TransactionLabel){s.int});
json_get_field("$evapi(msg)", "Attributes", "$var(cgrAttributes)");
$var(cgrAttributes) = $(var(cgrAttributes){s.rm,"});
json_get_field("$evapi(msg)", "ResourceAllocation", "$var(cgrResourceAllocation)");
$var($var(cgrResourceAllocation)) = $(var(cgrResourceAllocation){s.rm,"});
json_get_field("$evapi(msg)", "MaxUsage", "$var(MaxUsage)");
$var(cgrMaxUsage) = $(var(MaxUsage){s.int});
json_get_field("$evapi(msg)", "Routes", "$var(cgrRoutes)");
$var($var(cgrRoutes)) = $(var(cgrRoutes){s.rm,"});
json_get_field("$evapi(msg)", "Error", "$var(cgrError)");
$var(cgrError) = $(var(cgrError){s.rm,"});
t_continue("$var(id_index)", "$var(id_label)", "CGRATES_SESSIONAUTH_REPLY"); # Unpark the transaction
}
# 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\",
\"h_entry\":\"$dlg(h_entry)\",
\"h_id\":\"$dlg(h_id)\",
\"cgr_flags\":\"*attributes;*accounts;*resources;*thresholds\",
\"OriginID\":\"$dlg_var(cgrOriginID)\",
\"RequestType\":\"$dlg_var(cgrReqType)\",
\"Tenant\":\"$dlg_var(cgrTenant)\",
\"Account\":\"$dlg_var(cgrAccount)\",
\"Destination\":\"$dlg_var(cgrDestination)\",
\"SetupTime\":\"$dlg_var(SetupTime)\",
\"AnswerTime\":\"$TS\"}");
}
# 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\",
\"cgr_flags\":\"*accounts;*resources\",
\"OriginID\":\"$dlg_var(cgrOriginID)\",
\"RequestType\":\"$dlg_var(cgrReqType)\",
\"Tenant\":\"$dlg_var(cgrTenant)\",
\"Account\":\"$dlg_var(cgrAccount)\",
\"Destination\":\"$dlg_var(cgrDestination)\",
\"AnswerTime\":\"$dlg(start_ts)\",
\"PaypalAccount\":\"$dlg_var(paypalAccount)\",
\"SetupTime\":\"$dlg_var(SetupTime)\",
\"Usage\":\"$var(callDur)\"}");
}
cgrates/data/tutorials/kamevapi at master · cgrates/cgrates · GitHub