|
|
######## DoS prevention module ########
|
|
|
modparam("htable", "timer_interval", 10)
|
|
|
modparam("htable", "htable", "rps=>size=8;initval=0;autoexpire=60")
|
|
|
modparam("htable", "htable", "rpm=>size=8;initval=0;autoexpire=180")
|
|
|
modparam("htable", "htable", "tps=>size=8;initval=0;autoexpire=60")
|
|
|
modparam("htable", "htable", "tpm=>size=8;initval=0;autoexpire=180")
|
|
|
modparam("htable", "htable", "rate_limits=>initval=-1;autoexpire=60") # initval = -1 means that record is expired and we need an update from DB
|
|
|
|
|
|
#!trydef RATE_LIMIT_MESSAGE "Over rate Limit"
|
|
|
#!trydef RATE_LIMIT_CODE "603"
|
|
|
|
|
|
#!trydef IP_REGEX "[0-9]{1,3}\.[0-9]{1,3}.[0-9]{1,3}\.[0-9]{1,3}"
|
|
|
|
|
|
route[DOS_PREVENTION] {
|
|
|
|
|
|
# If packet came from platform or from 4 class MERA, do not check it
|
|
|
if (isflagset(FLAG_INTERNALLY_SOURCED) || isflagset(FLAG_TRUSTED_SOURCE) ) {
|
|
|
xlog("L_DEBUG", "$ci|limiter|trusted source IP($si) ignoring\n");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
# Initially we do not want to get data
|
|
|
$var(with-realm-request) = "false";
|
|
|
$var(with-realm-total) = "false";
|
|
|
$var(with-device-request) = "false";
|
|
|
$var(with-device-total) = "false";
|
|
|
$var(method-key) = "Method";
|
|
|
$var(method-value) = "\"TOTAL\"";
|
|
|
|
|
|
# SIP methods INVITE and REGISTER have personal counters
|
|
|
if ((is_method("INVITE") || is_method("REGISTER")) && (!isflagset(FLAG_IS_REPLY))) {
|
|
|
$var(lrpm_realm) = $fd+"/"+$rm+"/min";
|
|
|
$var(lrps_realm) = $fd+"/"+$rm+"/sec";
|
|
|
|
|
|
$var(lrpm_device) = $fU+"@"+$fd+"/"+$rm+"/min";
|
|
|
$var(lrps_device) = $fU+"@"+$fd+"/"+$rm+"/sec";
|
|
|
$var(method-value) = "\"" + $rm + "\"";
|
|
|
}
|
|
|
|
|
|
# For BYE method we use REALM from To SIP header
|
|
|
if ($fd =~ IP_REGEX) {
|
|
|
xlog("L_WARNING","$ci|limiter| Fixup for $rm method with IP in from URI: use to-domain\n");
|
|
|
$var(ltpm_realm) = $td+"/TOTAL/min";
|
|
|
$var(ltps_realm) = $td+"/TOTAL/sec";
|
|
|
$var(ltpm_device) = $fU+"@"+$td+"/TOTAL/min";
|
|
|
$var(ltps_device) = $fU+"@"+$td+"/TOTAL/sec";
|
|
|
$var(entity) = $td;
|
|
|
} else {
|
|
|
$var(ltpm_realm) = $fd+"/TOTAL/min";
|
|
|
$var(ltps_realm) = $fd+"/TOTAL/sec";
|
|
|
$var(ltpm_device) = $fU+"@"+$fd+"/TOTAL/min";
|
|
|
$var(ltps_device) = $fU+"@"+$fd+"/TOTAL/sec";
|
|
|
$var(entity) = $fd;
|
|
|
}
|
|
|
|
|
|
# REALM check
|
|
|
if ((is_method("INVITE") || is_method("REGISTER")) && (!isflagset(FLAG_IS_REPLY))) {
|
|
|
if ($sht(rate_limits=>$var(lrpm_realm)) == -1
|
|
|
|| $sht(rate_limits=>$var(lrps_realm)) == -1) {
|
|
|
xlog("L_INFO", "$ci|limiter|can't find HASHed rate for $var(entity) with $rm method\n");
|
|
|
$var(with-realm-request) = "true";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if ($sht(rate_limits=>$var(ltpm_realm)) == -1
|
|
|
|| $sht(rate_limits=>$var(ltps_realm)) == -1) {
|
|
|
xlog("L_INFO", "$ci|limiter|can't find HASHed rate for $var(entity) with $rm method\n");
|
|
|
$var(with-realm-total) = "true";
|
|
|
}
|
|
|
|
|
|
if (not_empty("$fU")) {
|
|
|
if ($fd =~ IP_REGEX) {
|
|
|
xlog("L_WARNING","$ci|limiter|fixup for $rm method with IP in from URI: use to-domain\n");
|
|
|
$var(entity) = $fU+"@"+$td;
|
|
|
} else {
|
|
|
$var(entity) = $fU+"@"+$fd;
|
|
|
}
|
|
|
|
|
|
#DEVICE check
|
|
|
if ((is_method("INVITE") || is_method("REGISTER")) && (!isflagset(FLAG_IS_REPLY))) {
|
|
|
if ($sht(rate_limits=>$var(lrpm_device)) == -1
|
|
|
|| $sht(rate_limits=>$var(lrps_device)) == -1) {
|
|
|
xlog("L_INFO", "$ci|limiter|can't find HASHed rate for $var(entity) with $rm method\n");
|
|
|
$var(with-device-request) = "true";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if ($sht(rate_limits=>$var(ltpm_device)) == -1 || $sht(rate_limits=>$var(ltps_device)) == -1) {
|
|
|
xlog("L_INFO", "$ci|limiter| can't find HASHed rate for $var(entity) with $rm method\n");
|
|
|
$var(with-device-total) = "true";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if ((is_method("INVITE") || is_method("REGISTER"))
|
|
|
&& (($var(with-device-request) == "true" && $var(with-device-total) == "true")
|
|
|
|| ($var(with-realm-request) == "true" && $var(with-realm-total) == "true"))) {
|
|
|
$var(method-key) = "Method-List";
|
|
|
$var(method-value) = "[\"" + $rm + "\", \"TOTAL\"]";
|
|
|
}
|
|
|
|
|
|
if ( $var(with-device-request) == "true"
|
|
|
|| $var(with-device-total) == "true"
|
|
|
|| $var(with-realm-request) == "true"
|
|
|
|| $var(with-realm-total) == "true" ) {
|
|
|
|
|
|
avp_printf("$avp(s:query-request)", "{\"Entity\" : \"$var(entity)\", \"$var(method-key)\" : $var(method-value), \"Event-Category\" : \"rate_limit\", \"Event-Name\" : \"query\", \"With-Realm\" : $var(with-realm-request)}");
|
|
|
xlog("L_INFO", "$ci|limiter|query: $avp(s:query-request)\n");
|
|
|
sl_send_reply("100", "Attempting K query");
|
|
|
if (kazoo_query("frontier", "sbc_config", $avp(s:query-request), "$var(amqp_result)")) {
|
|
|
xlog("L_INFO", "$ci|limiter|response: $var(amqp_result)\n");
|
|
|
|
|
|
kazoo_json($var(amqp_result), "Realm.Minute." + $rm, "$var(realm-min)");
|
|
|
kazoo_json($var(amqp_result), "Realm.Second." + $rm, "$var(realm-sec)");
|
|
|
kazoo_json($var(amqp_result), "Realm.Minute.TOTAL", "$var(realm-min-total)");
|
|
|
kazoo_json($var(amqp_result), "Realm.Second.TOTAL", "$var(realm-sec-total)");
|
|
|
kazoo_json($var(amqp_result), "Device.Minute." + $rm, "$var(device-min)");
|
|
|
kazoo_json($var(amqp_result), "Device.Second." + $rm, "$var(device-sec)");
|
|
|
kazoo_json($var(amqp_result), "Device.Minute.TOTAL", "$var(device-min-total)");
|
|
|
kazoo_json($var(amqp_result), "Device.Second.TOTAL", "$var(device-sec-total)");
|
|
|
|
|
|
if ( not_empty("$var(realm-min)") ) {
|
|
|
$sht(rate_limits=>$var(lrpm_realm)) = $(var(realm-min){s.int});
|
|
|
xlog("L_INFO", "$ci|limiter| $rm DB=>HASH for $var(lrpm_realm)=$sht(rate_limits=>$var(lrpm_realm))\n");
|
|
|
}
|
|
|
if ( not_empty("$var(realm-sec)") ) {
|
|
|
$sht(rate_limits=>$var(lrps_realm)) = $(var(realm-sec){s.int});
|
|
|
xlog("L_INFO", "$ci|limiter| $rm DB=>HASH for $var(lrps_realm)=$sht(rate_limits=>$var(lrps_realm))\n");
|
|
|
}
|
|
|
if ( not_empty("$var(realm-min-total)") ) {
|
|
|
$sht(rate_limits=>$var(ltpm_realm)) = $(var(realm-min-total){s.int});
|
|
|
xlog("L_INFO", "$ci|limiter| $rm DB=>HASH for $var(ltpm_realm)=$sht(rate_limits=>$var(ltpm_realm))\n");
|
|
|
}
|
|
|
if ( not_empty("$var(realm-sec-total)") ) {
|
|
|
$sht(rate_limits=>$var(ltps_realm)) = $(var(realm-sec-total){s.int});
|
|
|
xlog("L_INFO", "$ci|limiter| $rm DB=>HASH for $var(ltps_realm)=$sht(rate_limits=>$var(ltps_realm))\n");
|
|
|
}
|
|
|
if ( not_empty("$var(device-min)") ) {
|
|
|
$sht(rate_limits=>$var(lrpm_device)) = $(var(device-min){s.int});
|
|
|
xlog("L_INFO", "$ci|limiter| $rm DB=>HASH for $var(lrpm_device)=$sht(rate_limits=>$var(lrpm_device))\n");
|
|
|
}
|
|
|
if ( not_empty("$var(device-sec)") ) {
|
|
|
$sht(rate_limits=>$var(lrps_device)) = $(var(device-sec){s.int});
|
|
|
xlog("L_INFO", "$ci|limiter| $rm DB=>HASH for $var(lrps_device)=$sht(rate_limits=>$var(lrps_device))\n");
|
|
|
}
|
|
|
if ( not_empty("$var(device-min-total)") ) {
|
|
|
$sht(rate_limits=>$var(ltpm_device)) = $(var(device-min-total){s.int});
|
|
|
xlog("L_INFO", "$ci|limiter| $rm DB=>HASH for $var(ltpm_device)=$sht(rate_limits=>$var(ltpm_device))\n");
|
|
|
}
|
|
|
if ( not_empty("$var(device-sec-total)") ) {
|
|
|
$sht(rate_limits=>$var(ltps_device)) = $(var(device-sec-total){s.int});
|
|
|
xlog("L_INFO", "$ci|limiter| $rm DB=>HASH for $var(ltps_device)=$sht(rate_limits=>$var(ltps_device))\n");
|
|
|
}
|
|
|
} else {
|
|
|
xlog("L_ERROR", "$ci|limiter| $rm DB unreachable for entity: $var(entity)\n");
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if ($fd =~ IP_REGEX) {
|
|
|
xlog("L_WARNING","$ci|limiter| Fixup for $rm method with IP in from URI: use to-domain\n");
|
|
|
$var(entity) = $td;
|
|
|
} else {
|
|
|
$var(entity) = $fd;
|
|
|
}
|
|
|
$var(entity-type) = "realm";
|
|
|
if ((is_method("INVITE") || is_method("REGISTER")) && (!isflagset(FLAG_IS_REPLY))) {
|
|
|
$var(lrpm) = $sht(rate_limits=>$var(lrpm_realm));
|
|
|
$var(lrps) = $sht(rate_limits=>$var(lrps_realm));
|
|
|
}
|
|
|
$var(ltpm) = $sht(rate_limits=>$var(ltpm_realm));
|
|
|
$var(ltps) = $sht(rate_limits=>$var(ltps_realm));
|
|
|
|
|
|
|
|
|
route(DO_DOS_PREVENTION);
|
|
|
if ( not_empty("$fU") ) {
|
|
|
if ($fd =~ IP_REGEX) {
|
|
|
$var(entity) = $fU+"@"+$td;
|
|
|
xlog("L_WARNING","$ci|limiter| Fixup for $rm method with IP in from URI: use to-domain\n");
|
|
|
} else {
|
|
|
$var(entity) = $fU+"@"+$fd;
|
|
|
}
|
|
|
$var(entity-type) = "device";
|
|
|
if ((is_method("INVITE") || is_method("REGISTER")) && (!isflagset(FLAG_IS_REPLY))) {
|
|
|
$var(lrpm) = $sht(rate_limits=>$var(lrpm_device));
|
|
|
$var(lrps) = $sht(rate_limits=>$var(lrps_device));
|
|
|
}
|
|
|
$var(ltpm) = $sht(rate_limits=>$var(ltpm_device));
|
|
|
$var(ltps) = $sht(rate_limits=>$var(ltps_device));
|
|
|
route(DO_DOS_PREVENTION);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
# This route do counting and decide either to ACCEPT or DECLINE packet
|
|
|
route[DO_DOS_PREVENTION] {
|
|
|
# Personal counters for INVITE and REGISTER
|
|
|
if ((is_method("INVITE") || is_method("REGISTER"))) {
|
|
|
$var(rpm) = $var(entity)+":"+$rm+":min:"+$timef(%Y/%m/%d_%H_%M_00);
|
|
|
$var(rps) = $var(entity)+":"+$rm+":sec:"+$timef(%Y/%m/%d_%H_%M_%S);
|
|
|
}
|
|
|
# Commmon counters for ALL packet including INVITE and REGISTER
|
|
|
$var(tpm) = $var(entity)+":TOTAL:min:"+$timef(%Y/%m/%d_%H_%M_00);
|
|
|
$var(tps) = $var(entity)+":TOTAL:sec:"+$timef(%Y/%m/%d_%H_%M_%S);
|
|
|
|
|
|
# Personal debug for INVITE and REGISTER
|
|
|
if ((is_method("INVITE") || is_method("REGISTER"))) {
|
|
|
xlog("L_INFO", "$ci|limiter| L/C for $var(rpm) = $var(lrpm)/$sht(rpm=>$var(rpm))\n");
|
|
|
xlog("L_INFO", "$ci|limiter| L/C for $var(rps) = $var(lrps)/$sht(rps=>$var(rps))\n");
|
|
|
}
|
|
|
# Commmon debug for ALL packet including INVITE and REGISTER
|
|
|
xlog("L_INFO", "$ci|limiter| L/C for $var(tpm) = $var(ltpm)/$sht(tpm=>$var(tpm))\n");
|
|
|
xlog("L_INFO", "$ci|limiter| L/C for $var(tps) = $var(ltps)/$sht(tps=>$var(tps))\n");
|
|
|
|
|
|
# Personal increment just for INVITE and REGISTER
|
|
|
if ((is_method("INVITE") || is_method("REGISTER")) && (!isflagset(FLAG_IS_REPLY))) {
|
|
|
$sht(rpm=>$var(rpm)) = $shtinc(rpm=>$var(rpm));
|
|
|
$sht(rps=>$var(rps)) = $shtinc(rps=>$var(rps));
|
|
|
}
|
|
|
# Commmon increment for ALL packet including INVITE and REGISTER
|
|
|
$sht(tpm=>$var(tpm)) = $shtinc(tpm=>$var(tpm));
|
|
|
$sht(tps=>$var(tps)) = $shtinc(tps=>$var(tps));
|
|
|
|
|
|
# Personal checks for INVITE and REGISTER
|
|
|
if ((is_method("INVITE") || is_method("REGISTER")) && (!isflagset(FLAG_IS_REPLY))) {
|
|
|
if ($sht(rps=>$var(rps)) > $var(lrps)) {
|
|
|
sl_send_reply(RATE_LIMIT_CODE, RATE_LIMIT_MESSAGE);
|
|
|
xlog("L_INFO", "$ci|limiter| Out of $rm $var(rps) rate limits: $sht(rps=>$var(rps)) > $var(lrps))\n");
|
|
|
exit;
|
|
|
}
|
|
|
if ($sht(rpm=>$var(rpm)) > $var(lrpm)) {
|
|
|
sl_send_reply(RATE_LIMIT_CODE, RATE_LIMIT_MESSAGE);
|
|
|
xlog("L_INFO", "$ci|limiter| Out of $rm $var(rpm) rate limits: $sht(rpm=>$var(rpm)) > $var(lrpm))\n");
|
|
|
exit;
|
|
|
}
|
|
|
}
|
|
|
# Commmon checks for ALL packet including INVITE and REGISTER
|
|
|
if ($sht(tps=>$var(tps)) > $var(ltps)) {
|
|
|
if (isflagset(FLAG_IS_REPLY)) {
|
|
|
xlog("L_INFO", "$ci|limiter| Out of TOTAL($rm::$rs $rr) $var(tps) rate limits: $sht(tps=>$var(tps)) > $var(ltps))\n");
|
|
|
} else {
|
|
|
sl_send_reply(RATE_LIMIT_CODE, RATE_LIMIT_MESSAGE);
|
|
|
xlog("L_INFO", "$ci|limiter| Out of TOTAL($rm) $var(tps) rate limits: $sht(tps=>$var(tps)) > $var(ltps))\n");
|
|
|
}
|
|
|
exit;
|
|
|
}
|
|
|
if ($sht(tpm=>$var(tpm)) > $var(ltpm)) {
|
|
|
if (isflagset(FLAG_IS_REPLY)) {
|
|
|
xlog("L_INFO", "$ci|limiter| Out of TOTAL($rm::$rs $rr) $var(tpm) rate limits: $sht(tpm=>$var(tpm)) > $var(ltpm))\n");
|
|
|
} else {
|
|
|
sl_send_reply(RATE_LIMIT_CODE, RATE_LIMIT_MESSAGE);
|
|
|
xlog("L_INFO", "$ci|limiter| Out of TOTAL($rm) $var(tpm) rate limits: $sht(tpm=>$var(tpm)) > $var(ltpm))\n");
|
|
|
}
|
|
|
exit;
|
|
|
}
|
|
|
}
|