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

118
admin.js
View File

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

View File

@@ -1,18 +1,18 @@
{ {
"groups": [ "groups": [
{"id": "alibaba", "name": "Alibaba"}, {"id": "alibaba", "name": "Alibaba"},
{"id": "amazon", "name": "Amazon Data Services"}, {"id": "amazon", "name": "Amazon DS"},
{"id": "amazon-microsoft", "name": "Amazon or Microsoft"}, {"id": "amazon-microsoft", "name": "Amazon or Microsoft"},
{"id": "brasilnet", "name": "BrasilNet"}, {"id": "brasilnet", "name": "BrasilNet"},
{"id": "charter", "name": "Charter Communications Inc."}, {"id": "charter", "name": "Charter Inc."},
{"id": "chinanet", "name": "Chinanet"}, {"id": "chinanet", "name": "Chinanet"},
{"id": "cnisp", "name": "China ISP"}, {"id": "cnisp", "name": "China ISP"},
{"id": "cnmob", "name": "China Mobile Communications Corp."}, {"id": "cnmob", "name": "China Mobile"},
{"id": "google", "name": "Google LLC"}, {"id": "google", "name": "Google LLC"},
{"id": "google-amazon", "name": "Google or Amazon"}, {"id": "google-amazon", "name": "Google or Amazon"},
{"id": "hetzner", "name": "Hetzner Hosting (US)"}, {"id": "hetzner", "name": "Hetzner US"},
{"id": "huawei", "name": "Huawei"}, {"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": "tencent", "name": "Tencent"},
{"id": "unicom", "name": "China Unicom"}, {"id": "unicom", "name": "China Unicom"},
{"id": "vnpt", "name": "Vietnam Telecom"}, {"id": "vnpt", "name": "Vietnam Telecom"},
@@ -35,7 +35,7 @@
{"from": "44.192.0.0", "to": "44.255.255.254", "m": 16, "g": "tencent"}, {"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": "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": "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": "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": "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"}, {"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": "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": "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": "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": "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": "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": "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": "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": "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": "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"}, {"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') { if ($bName == '' || $bName == 'logfiles' || $bName == 'empty' || $fName == '.htaccess') {
// echo "File “{$fName}” ignored.\n"; // echo "File “{$fName}” ignored.\n";
} else if ($bName == $today || $bName == $yesterday) { } 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 { } else {
if (unlink(DOKU_PLUGIN . 'botmon/logs/' . $file)) { if (unlink(DOKU_PLUGIN . 'botmon/logs/' . $file)) {
echo "<li class='success'>File “{$fName}” deleted.</li>\n"; 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