Better Bot IP-Ranges/Networks detection

This commit is contained in:
Sascha Leib
2025-10-17 15:47:50 +02:00
parent bda325f4f6
commit 20997f50c5
5 changed files with 102 additions and 41 deletions

View File

@@ -37,6 +37,8 @@
&.ipaddr::before { background-image: url('img/addr.png') }
&.ip4::before { background-position-y: -20px }
&.ip6::before { background-position-y: -40px }
&.ipnet::before { background-position-y: -60px }
&.ipother::before { background-image: url('img/more.svg') }
/* Bot icons */
&.bot::before { background-image: url('img/bots.png') }
@@ -784,7 +786,7 @@
&:nth-child(even) {
background-color: #DFDFDF;
}
&.info { color: #626262; font-style: italic;}
&.skipped { color: #626262; font-style: italic;}
&.success { color: #217121; }
&.error { color: #bb2929; }
}

118
admin.js
View File

@@ -824,6 +824,7 @@ BotMon.live = {
const other = {
'id': 'other',
'name': "Others",
'typ': 'other',
'count': 0
};
let total = 0; // adding up the items
@@ -834,6 +835,7 @@ BotMon.live = {
const rIt = {
id: it.id,
name: (it.n ? it.n : it.id),
typ: it.typ || it.id,
count: it.count
};
rList.push(rIt);
@@ -1001,7 +1003,7 @@ BotMon.live = {
return bounces;
},
_ipOwners: [],
_ipRanges: [],
/* adds a visit to the ip ranges arrays */
addToIpRanges: function(v) {
@@ -1012,34 +1014,86 @@ BotMon.live = {
// Number of IP address segments to look at:
const kIP4Segments = 1;
const kIP6Segments = 2;
const kIP6Segments = 3;
let isp = 'null'; // default ISP id
let name = 'Unknown'; // default ISP name
let ipGroup = ''; // group name
let ipSeg = []; // IP segment array
let rawIP = ''; // raw ip prefix
let ipName = ''; // IP group display name
const ipType = v.ip.indexOf(':') > 0 ? BM_IPVERSION.IPv6 : BM_IPVERSION.IPv4;
const ipAddr = BotMon.t._ip2Num(v.ip);
// is there already a known IP range assigned?
if (v._ipRange) {
isp = v._ipRange.g;
}
let ispRec = me._ipOwners.find( it => it.id == isp);
if (!ispRec) {
ispRec = {
'id': isp,
'n': ipRanges.getOwner( isp ) || "Unknown",
'count': v._pageViews.length
};
me._ipOwners.push(ispRec);
} else {
ispRec.count += v._pageViews.length;
ipGroup = v._ipRange.g; // group name
ipName = ipRanges.getOwner( v._ipRange.g ) || "Unknown";
} else { // no known IP range, let's collect necessary information:
// collect basic IP address info:
if (ipType == BM_IPVERSION.IPv6) {
ipSeg = ipAddr.split(':');
const prefix = v.ip.split(':').slice(0, kIP6Segments).join(':');
rawIP = ipSeg.slice(0, kIP6Segments).join(':');
ipGroup = 'ip6-' + rawIP.replaceAll(':', '-');
ipName = prefix + '::' + '/' + (16 * kIP6Segments);
} else {
ipSeg = ipAddr.split('.');
const prefix = v.ip.split('.').slice(0, kIP4Segments).join('.');
rawIP = ipSeg.slice(0, kIP4Segments).join('.') ;
ipGroup = 'ip4-' + rawIP.replaceAll('.', '-');
ipName = prefix + '.0.0.0'.substring(0, 1+(4-kIP4Segments)*2) + '/' + (8 * kIP4Segments);
}
}
// check if record already exists:
let ipRec = me._ipRanges.find( it => it.g == ipGroup);
if (!ipRec) {
// ip info record initialised:
ipRec = {
g: ipGroup,
n: ipName,
count: 0
}
// existing record?
if (v._ipRange) {
ipRec.from = v._ipRange.from;
ipRec.to = v._ipRange.to;
ipRec.typ = 'net';
} else { // no known IP range, let's collect necessary information:
// complete the ip info record:
if (ipType == BM_IPVERSION.IPv6) {
ipRec.from = rawIP + ':0000:0000:0000:0000:0000:0000:0000'.substring(0, (8-kIP6Segments)*5);
ipRec.to = rawIP + ':FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF'.substring(0, (8-kIP6Segments)*5);
ipRec.typ = '6';
} else {
ipRec.from = rawIP + '.000.000.000.000'.substring(0, (4-kIP4Segments)*4);
ipRec.to = rawIP + '.255.255.255.254'.substring(0, (4-kIP4Segments)*4);
ipRec.typ = '4';
}
}
me._ipRanges.push(ipRec);
}
// add to counter:
ipRec.count += v._pageViews.length;
},
getTopBotISPs: function(max) {
const me = BotMon.live.data.analytics;
return me._makeTopList(me._ipOwners, max);
return me._makeTopList(me._ipRanges, max);
},
},
@@ -1817,6 +1871,8 @@ BotMon.live = {
const maxItemsPerList = 5; // how many list items to show?
const kNoData = ''; // shown when data is missing
// shortcut for neater code:
const makeElement = BotMon.t._makeElement;
@@ -1831,23 +1887,23 @@ BotMon.live = {
switch(i) {
case 0:
title = "Known bots:";
value = data.bots.known;
value = data.bots.known || kNoData;
break;
case 1:
title = "Suspected bots:";
value = data.bots.suspected;
value = data.bots.suspected || kNoData;
break;
case 2:
title = "Probably humans:";
value = data.bots.human;
value = data.bots.human || kNoData;
break;
case 3:
title = "Registered users:";
value = data.bots.users;
value = data.bots.users || kNoData;
break;
case 4:
title = "Total:";
value = data.totalPageViews;
value = data.totalPageViews || kNoData;
break;
case 5:
title = "Bots-humans ratio:";
@@ -1879,13 +1935,13 @@ BotMon.live = {
// update the suspected bot IP ranges list:
const botIps = document.getElementById('botmon__botips');
if (botIps) {
botIps.appendChild(makeElement('dt', {}, "Top bot ISPs"));
botIps.appendChild(makeElement('dt', {}, "Top bot Networks"));
const ispList = BotMon.live.data.analytics.getTopBotISPs(5);
console.log(ispList);
//console.log(ispList);
ispList.forEach( (netInfo) => {
const li = makeElement('dd');
li.appendChild(makeElement('span', {'class': 'has_icon ipaddr ip_' + netInfo.id }, netInfo.name));
li.appendChild(makeElement('span', {'class': 'has_icon ipaddr ip' + netInfo.typ }, netInfo.name));
li.appendChild(makeElement('span', {'class': 'count' }, netInfo.count));
botIps.append(li)
});
@@ -1918,20 +1974,20 @@ BotMon.live = {
let value = '';
switch(i) {
case 0:
title = "Page views by registered users:";
value = data.bots.users;
title = "Registered users page views:";
value = data.bots.users || kNoData;
break;
case 1:
title = "Page views by “probably humans”:";
value = data.bots.human;
title = "Probably humans” page views:";
value = data.bots.human || kNoData;
break;
case 2:
title = "Total human page views:";
value = data.bots.users + data.bots.human;
value = (data.bots.users + data.bots.human) || kNoData;
break;
case 3:
title = "Total human visits:";
value = humanVisits;
value = humanVisits || kNoData;
break;
case 4:
title = "Humans bounce rate:";

View File

@@ -1,18 +1,18 @@
{
"groups": [
{"id": "alibaba", "name": "Alibaba"},
{"id": "amazon", "name": "Amazon Data Services"},
{"id": "amazon", "name": "Amazon DS"},
{"id": "amazon-microsoft", "name": "Amazon or Microsoft"},
{"id": "brasilnet", "name": "BrasilNet"},
{"id": "charter", "name": "Charter Communications Inc."},
{"id": "charter", "name": "Charter Inc."},
{"id": "chinanet", "name": "Chinanet"},
{"id": "cnisp", "name": "China ISP"},
{"id": "cnmob", "name": "China Mobile Communications Corp."},
{"id": "cnmob", "name": "China Mobile"},
{"id": "google", "name": "Google LLC"},
{"id": "google-amazon", "name": "Google or Amazon"},
{"id": "hetzner", "name": "Hetzner Hosting (US)"},
{"id": "hetzner", "name": "Hetzner US"},
{"id": "huawei", "name": "Huawei"},
{"id": "misc_sa", "name": "Various South- and Central-American ISPs"},
{"id": "misc_sa", "name": "Misc. SA ISPs"},
{"id": "tencent", "name": "Tencent"},
{"id": "unicom", "name": "China Unicom"},
{"id": "vnpt", "name": "Vietnam Telecom"},
@@ -35,7 +35,7 @@
{"from": "44.192.0.0", "to": "44.255.255.254", "m": 16, "g": "tencent"},
{"from": "45.0.0.0", "to": "45.255.255.254", "m": 10, "g": "amazon"},
{"from": "46.250.160.0", "to": "46.250.191.254", "m": 19, "g": "huawei"},
{"from": "47.82.0.0", "to": "47.82.255.254", "m": 16, "g": "alibaba"},
{"from": "47.74.0.0", "to": "47.127.255.254", "m": 10, "g": "alibaba"},
{"from": "47.200.0.0", "to": "47.203.255.254", "m": 14, "g": "frontier"},
{"from": "49.0.192.0", "to": "49.0.255.254", "m": 18, "g": "huawei"},
{"from": "52.0.0.0", "to": "52.255.255.254", "m": 8, "g": "amazon-microsoft"},
@@ -67,11 +67,14 @@
{"from": "162.128.0.0", "to": "162.128.255.254", "m": 16, "g": "zenlayer"},
{"from": "166.108.192.0", "to": "166.108.255.254", "m": 18, "g": "huawei"},
{"from": "168.232.192.0", "to": "168.232.255.254", "m": 16, "g": "misc_sa"},
{"from": "170.82.0.0", "to": "170.82.255.254", "m": 16, "g": "misc_sa"},
{"from": "170.0.0.0", "to": "170.3.255.254", "m": 14, "g": "misc_sa"},
{"from": "170.80.0.0", "to": "170.83.255.254", "m": 14, "g": "misc_sa"},
{"from": "170.254.0.0", "to": "170.254.255.254", "m": 16, "g": "misc_sa"},
{"from": "171.224.0.0", "to": "171.239.255.254", "m": 12, "g": "viettel"},
{"from": "177.0.0.0", "to": "177.255.255.254", "m": 8, "g": "brasilnet"},
{"from": "178.156.128.0", "to": "178.156.255.254", "m": 17, "g": "hetzner"},
{"from": "179.0.0.0", "to": "179.255.255.254", "m": 8, "g": "brasilnet"},
{"from": "181.0.0.0", "to": "181.255.255.254", "m": 8, "g": "misc_sa"},
{"from": "183.87.32.0", "to": "183.87.63.254", "m": 19, "g": "huawei"},
{"from": "186.0.0.0", "to": "186.255.255.254", "m": 8, "g": "misc_sa"},
{"from": "187.0.0.0", "to": "187.255.255.254", "m": 8, "g": "misc_sa"},

View File

@@ -35,7 +35,7 @@ class helper_plugin_botmon extends Plugin {
if ($bName == '' || $bName == 'logfiles' || $bName == 'empty' || $fName == '.htaccess') {
// echo "File “{$fName}” ignored.\n";
} else if ($bName == $today || $bName == $yesterday) {
echo "<li class='info'>File “{$fName}” skipped.</li>\n";
echo "<li class='skipped'>File “{$fName}” skipped.</li>\n";
} else {
if (unlink(DOKU_PLUGIN . 'botmon/logs/' . $file)) {
echo "<li class='success'>File “{$fName}” deleted.</li>\n";
@@ -44,7 +44,7 @@ class helper_plugin_botmon extends Plugin {
}
}
}
echo "<li class='info'>Done.</li>\n";
echo "<li>Done.</li>\n";
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB