Files
rudimentary-ids/autoban.mjs
2026-02-13 00:30:12 +01:00

121 lines
2.6 KiB
JavaScript

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();
});