1
0
mirror of https://github.com/KubaPro010/fm-dx-webserver.git synced 2026-02-26 22:13:53 +01:00

add per-ip limit

This commit is contained in:
Amateur Audio Dude
2025-07-07 22:38:20 +10:00
committed by GitHub
parent 4ece21d513
commit 2ab7dd33dd
3 changed files with 101 additions and 32 deletions

View File

@@ -383,6 +383,24 @@ function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userC
* WEBSOCKET BLOCK
*/
const tunerLockTracker = new WeakMap();
const ipConnectionCounts = new Map(); // Per-IP limit variables
const ipLogTimestamps = new Map();
const MAX_CONNECTIONS_PER_IP = 5;
const IP_LOG_INTERVAL_MS = 60000;
// Remove old per-IP limit addresses
setInterval(() => {
const now = Date.now();
for (const [ip, count] of ipConnectionCounts.entries()) {
const lastSeen = ipLogTimestamps.get(ip) || 0;
const inactive = now - lastSeen > 60 * 60 * 1000;
if (count === 0 && inactive) {
ipConnectionCounts.delete(ip);
ipLogTimestamps.delete(ip);
}
}
}, 60 * 60 * 1000); // Run every hour
wss.on('connection', (ws, request) => {
const output = serverConfig.xdrd.wirelessConnection ? client : serialport;
@@ -390,15 +408,44 @@ wss.on('connection', (ws, request) => {
const userCommandHistory = {};
const normalizedClientIp = clientIp?.replace(/^::ffff:/, '');
if (serverConfig.webserver.banlist?.includes(clientIp)) {
if (clientIp && serverConfig.webserver.banlist?.includes(clientIp)) {
ws.close(1008, 'Banned IP');
return;
}
if (clientIp.includes(',')) {
if (clientIp && clientIp.includes(',')) {
clientIp = clientIp.split(',')[0].trim();
}
// Per-IP limit connection open
if (clientIp) {
const isLocalIp = (
clientIp === '127.0.0.1' ||
clientIp === '::1' ||
clientIp === '::ffff:127.0.0.1' ||
clientIp.startsWith('192.168.') ||
clientIp.startsWith('10.') ||
clientIp.startsWith('172.16.')
);
if (!isLocalIp) {
if (!ipConnectionCounts.has(clientIp)) {
ipConnectionCounts.set(clientIp, 0);
}
const currentCount = ipConnectionCounts.get(clientIp);
if (currentCount >= MAX_CONNECTIONS_PER_IP) {
ws.close(1008, 'Too many open connections from this IP');
const lastLogTime = ipLogTimestamps.get(clientIp) || 0;
const now = Date.now();
if (now - lastLogTime > IP_LOG_INTERVAL_MS) {
logWarn(`Web client \x1b[31mclosed: limit exceeded\x1b[0m (${normalizedClientIp}) \x1b[90m[${currentUsers}]`);
ipLogTimestamps.set(clientIp, now);
}
return;
}
ipConnectionCounts.set(clientIp, currentCount + 1);
}
}
if (clientIp !== '::ffff:127.0.0.1' || (request.connection && request.connection.remoteAddress && request.connection.remoteAddress !== '::ffff:127.0.0.1') || (request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
currentUsers++;
}
@@ -472,6 +519,22 @@ wss.on('connection', (ws, request) => {
});
ws.on('close', (code, reason) => {
// Per-IP limit connection closed
if (clientIp) {
const isLocalIp = (
clientIp === '127.0.0.1' ||
clientIp === '::1' ||
clientIp === '::ffff:127.0.0.1' ||
clientIp.startsWith('192.168.') ||
clientIp.startsWith('10.') ||
clientIp.startsWith('172.16.')
);
if (!isLocalIp) {
const current = ipConnectionCounts.get(clientIp) || 1;
ipConnectionCounts.set(clientIp, Math.max(0, current - 1));
}
}
if (clientIp !== '::ffff:127.0.0.1' || (request.connection && request.connection.remoteAddress && request.connection.remoteAddress !== '::ffff:127.0.0.1') || (request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
currentUsers--;
}

View File

@@ -6,6 +6,7 @@ var parsedData, signalChart, previousFreq;
var data = [];
var signalData = [];
let updateCounter = 0;
let lastReconnectAttempt = 0;
let messageCounter = 0; // Count for WebSocket data length returning 0
let messageData = 800; // Initial value anything above 0
let messageLength = 800; // Retain value of messageData until value is updated
@@ -375,8 +376,14 @@ function sendPingRequest() {
messageCounter = 0;
}
// Automatic reconnection on WebSocket close
if (socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) {
// Automatic reconnection on WebSocket close with cooldown
const now = Date.now();
if (
(socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) &&
(now - lastReconnectAttempt > TIMEOUT_DURATION)
) {
lastReconnectAttempt = now;
socket = new WebSocket(socketAddress);
socket.onopen = () => {

View File

@@ -1,30 +1,29 @@
var url = new URL('text', window.location.href);
url.protocol = url.protocol.replace('http', 'ws');
var socketAddress = url.href;
var socket = new WebSocket(socketAddress);
if (!window.socket || window.socket.readyState === WebSocket.CLOSED || window.socket.readyState === WebSocket.CLOSING) {
var url = new URL('text', window.location.href);
url.protocol = url.protocol.replace('http', 'ws');
var socketAddress = url.href;
var socket = new WebSocket(socketAddress);
const socketPromise = new Promise((resolve, reject) => {
// Event listener for when the WebSocket connection is open
window.socket = socket;
const socketPromise = new Promise((resolve, reject) => {
socket.addEventListener('open', () => {
console.log('WebSocket connection open');
resolve(socket); // Resolve the promise with the WebSocket instance
resolve(socket);
});
// Event listener for WebSocket errors
socket.addEventListener('error', (error) => {
console.error('WebSocket error', error);
reject(error); // Reject the promise on error
reject(error);
});
// Event listener for WebSocket connection closure
socket.addEventListener('close', () => {
setTimeout(() => {
console.warn('WebSocket connection closed');
reject(new Error('WebSocket connection closed')); // Reject with closure warning
}, 100);
reject(new Error('WebSocket connection closed'));
});
});
});
// Assign the socketPromise to window.socketPromise for global access
window.socketPromise = socketPromise;
// Assign the socket instance to window.socket for global access
window.socket = socket;
window.socketPromise = socketPromise;
}