What you're seeing
REGISTER sip:203.0.113.10 SIP/2.0
Via: SIP/2.0/UDP 192.0.2.66:5160;branch=z9hG4bK-3521960414
From: "100" <sip:[email protected]>;tag=3434313732
User-Agent: friendly-scanner
ALERT: pike blocking REGISTER from sip:[email protected] (IP:192.0.2.66:5160)You'll see bursts of REGISTER/INVITE/OPTIONS to numeric users, often thousands a minute from one address, and CDRs with international INVITEs from unknown sources if something is open.
Likely causes
Most common first.
- Scanners get real replies. Every 401, 403 or 404 confirms a live SIP server and helps enumerate users. Known scanner user agents should be dropped silently, before any transaction or database lookup.
- No per-IP rate limit. The pike module counts requests per source IP over a sampling window and flags sources over the threshold. Without it one address can send thousands of requests a second and every one gets processed.
- Failed authentication isn't counted. Password guessing uses the scanner's normal rate, so pike rarely catches it. Count auth_check() failures per source IP in an htable and ban after a threshold.
- Bans live only inside Kamailio. An htable ban still costs a packet receive and parse per request. For persistent attackers, push the ban to the firewall with fail2ban (or nftables sets) keyed on Kamailio's log line.
How to fix
1. Drop scanners and flooders in REQINIT
loadmodule "pike.so"
loadmodule "htable.so"
loadmodule "ipops.so"
modparam("pike", "sampling_time_unit", 2)
modparam("pike", "reqs_density_per_unit", 16)
modparam("pike", "remove_latency", 4)
modparam("htable", "htable", "ipban=>size=8;autoexpire=300;")
# initval=0 makes $shtinc() count from zero; without it a missing key stays $null
modparam("htable", "htable", "authfail=>size=8;autoexpire=900;initval=0;")
route[REQINIT] {
set_reply_no_connect();
force_rport();
# carriers and PBXs (198.51.100.0/24 here) are never rate-limited or banned
if (!is_in_subnet("$si", "198.51.100.0/24")) {
if ($sht(ipban=>$si) != $null) {
exit;
}
if (!pike_check_req()) {
xlog("L_ALERT", "ALERT: pike blocking $rm from $fu (IP:$si:$sp)\n");
$sht(ipban=>$si) = 1;
exit;
}
if ($ua =~ "friendly|scanner|sipcli|sipvicious|VaxSIPUserAgent|pplsip|sipsak") {
$sht(ipban=>$si) = 1;
exit;
}
}
# ... keep the rest of the stock REQINIT: max-forwards check,
# OPTIONS keepalive reply and sanity_check() ...
}This is the stock 5.8 WITH_ANTIFLOOD logic, extended to ban scanner user agents and to exempt your own networks. exit without a reply means the scanner gets nothing back. route(REQINIT) is the first thing request_route runs, before t_check_trans() or anything that creates state.
2. Ban after repeated authentication failures
route[AUTH] {
if (is_method("REGISTER") || from_uri == myself) {
if (!auth_check("$fd", "subscriber", "1")) {
$var(rc) = $rc;
# -2 wrong password, -3 unknown user
if ($var(rc) == -2 || $var(rc) == -3) {
$var(fails) = $shtinc(authfail=>$si);
if ($var(fails) >= 10) {
xlog("L_ALERT", "ALERT: auth ban $si after $var(fails) failures\n");
$sht(ipban=>$si) = 1;
exit;
}
}
auth_challenge("$fd", "0");
exit;
}
if (!is_method("REGISTER|PUBLISH")) {
consume_credentials();
}
}
# never drop this: without it the proxy relays for anyone
if (!(uri == myself || from_uri == myself)) {
sl_send_reply("403", "Not relaying");
exit;
}
}3. Push bans to the firewall
[Definition]
failregex = ALERT: pike blocking .* \(IP:<HOST>:\d+\)
ALERT: auth ban <HOST> after[kamailio]
enabled = true
filter = kamailio
logpath = /var/log/kamailio.log
maxretry = 1
bantime = 3600
port = 5060,5061
protocol = allPoint logpath at wherever syslog writes Kamailio's messages. Keep is_in_subnet() and fail2ban's ignoreip in step with each other so you never ban your own carriers.