Sometimes, due to incorrect configuration, even not on the system we control, we can have call loops. Imagine a situation with loop call forward with one of the endpoints is a cell phone and forward is made via another operator. Usually, you can't get all these loops relying only on the internal pre-save mechanism.
But there is a simple and efficient method to avoid such loops. Idea is to limit calls from A to B, if there are more than X such calls per second.
Proposed solutions are Kamailio and Asterisk's implementation of these scenarios.
Kamailio method is based on htable
...
# Not more than FROM_TO_PER_SECOND_RATIO calls per second from X number to Y number. If it is set to 3, 4th call would be blocked
#!define FROM_TO_PER_SECOND_RATIO 3
...
loadmodule "htable.so"
modparam("htable", "htable", "fu_tu=>size=5;autoexpire=5;")
...
request_route{
...
# Process only new calls
if (is_method("INVITE") && !has_totag()) {
$var(from_to_hash) = $fU + '_' + $tU + '_' + $timef(%H_%M_%S);
if ($sht(fu_tu=>$var(from_to_hash)) != $null) {
$sht(fu_tu=>$var(from_to_hash)) = $sht(fu_tu=>$var(from_to_hash)) + 1;
xlog("L_NOTICE", "Possible loop detected on call $fu -> $tu ($sht(fu_tu=>$var(from_to_hash)) total calls during last second)\n");
} else {
xlog("L_INFO", "Loop protection enabled on call $fu -> $tu with $var(from_to_hash) hash)\n");
$sht(fu_tu=>$var(from_to_hash)) = 1;
}
if ($sht(fu_tu=>$var(from_to_hash)) > FROM_TO_PER_SECOND_RATIO) {
xlog("L_WARN", "Loop detected on call $fu -> $tu (IP:$si:$sp)\n");
send_reply("482", "Loop detected");
exit;
}
}
...
}
Asterisk method is based on GROUP()
[dial_all]
exten => _X.,1,NoOp(Main call context)
same => n,GoSub(loop_protection, ${EXTEN}, 1)
...
[loop_protection]
; Provide loop protection based on calls per second on same from/to numbers.
exten => _X.,1,NoOp(Loop protection...)
same => n,Set(GROUP_HASH=${CALLERID(num)}_${EXTEN}_${STRFTIME(${EPOCH},,%Y_%m_%d_%H_%M_%S)})
same => n,Set(GROUP()=${GROUP_HASH})
same => n,GoToIf($[${GROUP_COUNT(${GROUP_HASH})}<=3]?limit_ok)
same => n,NoOp(Total Calls From ${CALLERID(num)} to ${EXTEN} during this second exceeded limit of <%= @loop_cps_ratio %>)
same => n,Hangup(25)
same => n(limit_ok),NoOp(Total Calls From ${CALLERID(num)} to ${EXTEN} during this second is: ${GROUP_COUNT(${GROUP_HASH})})
same => n,Return()
The mechanism is fairly simple on both. Have a hash with the following name - <from_number>_<to_number>_<date_with_second>, calculate it for every new call, and if his hash name already exists - increase it by 1 till limit is reached. After this - drop the call, considering this is a loop.
Maybe not the best method, but reliable. Only thing I did not investigate much - possible memory leak on Asterisk with group names. I consider this is something to be tested.