Initial commit
This commit is contained in:
120
autoban.mjs
Normal file
120
autoban.mjs
Normal file
@@ -0,0 +1,120 @@
|
||||
import { readFileSync, realpathSync } from 'node:fs';
|
||||
import { parse_socket_state } from './parser.mjs';
|
||||
import { spawn } from 'node:child_process';
|
||||
|
||||
|
||||
// Configuration
|
||||
const network_interface = 'ens3';
|
||||
const update_frequency = 1; // Hz
|
||||
const seen_threshold = 5; // times
|
||||
const max_age = 20; // seconds
|
||||
const max_ban_time = 300; // seconds
|
||||
const dry_run = true;
|
||||
|
||||
// Use IGNORE_IPS in environment for comma separated list of IPs that will never be banned
|
||||
const ignore_ips = new Set(process.env.IGNORE_IPS?.split(',').map(e => e.trim()));
|
||||
|
||||
// Application
|
||||
|
||||
const peer_map = new Map();
|
||||
const ban_map = new Map();
|
||||
|
||||
const ban_script = realpathSync('./ban.sh');
|
||||
const unban_script = realpathSync('./unban.sh');
|
||||
|
||||
function sudo(argv) {
|
||||
if (dry_run) {
|
||||
console.log('sudo', ['-n', ...argv]);
|
||||
} else {
|
||||
spawn('sudo', ['-n', ...argv], { stdio: 'inherit', detached: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ban(ip) {
|
||||
const ts = performance.now();
|
||||
if (ignore_ips.has(ip)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = ban_map.get(ip);
|
||||
|
||||
if (existing) {
|
||||
existing.ts = ts;
|
||||
} else {
|
||||
ban_map.set(ip, { ip, ts });
|
||||
console.log(new Date().toISOString(), '**BAN**', ip);
|
||||
sudo([ban_script, network_interface, ip]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function check_ban_list() {
|
||||
const ts = performance.now();
|
||||
for (const entry of ban_map.values()) {
|
||||
const age = (ts - entry.ts) / 1000;
|
||||
if (age > max_ban_time) {
|
||||
ban_map.delete(entry.ip);
|
||||
console.log(new Date().toISOString(), 'UNBAN', entry.ip);
|
||||
sudo([unban_script, network_interface, ip]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function run_at_rate(rate, cb) {
|
||||
let monotime = performance.now();
|
||||
|
||||
function run_once() {
|
||||
monotime += 1000 / rate;
|
||||
cb();
|
||||
const remaining = Math.max(0, monotime - performance.now());
|
||||
setTimeout(run_once, remaining);
|
||||
}
|
||||
run_once();
|
||||
}
|
||||
|
||||
|
||||
run_at_rate(update_frequency, () => {
|
||||
|
||||
// Update once
|
||||
const socket_state = readFileSync('/proc/net/tcp', 'utf-8');
|
||||
for (const entry of parse_socket_state(socket_state)) {
|
||||
if ((entry.st & 0x3) === 0x3) {
|
||||
const ts = performance.now();
|
||||
const existing = peer_map.get(entry.rem_address);
|
||||
if (existing) {
|
||||
existing.count++;
|
||||
existing.ts = ts;
|
||||
} else {
|
||||
peer_map.set(entry.rem_address, { entry, ts, count: 1 });
|
||||
console.log(new Date().toISOString(), 'ADD', entry.rem_address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Purge old
|
||||
const ts = performance.now();
|
||||
for (const entry of peer_map.values()) {
|
||||
const age = (ts - entry.ts) / 1000;
|
||||
if (age > max_age) {
|
||||
peer_map.delete(entry.entry.rem_address);
|
||||
console.log(new Date().toISOString(), 'DROP', entry.entry.rem_address);
|
||||
}
|
||||
}
|
||||
|
||||
// Check limits
|
||||
for (const entry of peer_map.values()) {
|
||||
if (entry.count > seen_threshold) {
|
||||
ban(entry.entry.rem_address);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
check_ban_list();
|
||||
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user