You've already forked fm-dx-webserver
mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-02-26 14:11:59 +01:00
add per-ip limit
This commit is contained in:
committed by
GitHub
parent
4ece21d513
commit
2ab7dd33dd
@@ -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--;
|
||||
}
|
||||
|
||||
@@ -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,10 +376,16 @@ 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 = () => {
|
||||
sendToast('info', 'Connected', 'Reconnected successfully!', false, false);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
socket.addEventListener('open', () => {
|
||||
console.log('WebSocket connection open');
|
||||
resolve(socket); // Resolve the promise with the WebSocket instance
|
||||
window.socket = socket;
|
||||
|
||||
const socketPromise = new Promise((resolve, reject) => {
|
||||
socket.addEventListener('open', () => {
|
||||
console.log('WebSocket connection open');
|
||||
resolve(socket);
|
||||
});
|
||||
|
||||
socket.addEventListener('error', (error) => {
|
||||
console.error('WebSocket error', error);
|
||||
reject(error);
|
||||
});
|
||||
|
||||
socket.addEventListener('close', () => {
|
||||
setTimeout(() => {
|
||||
console.warn('WebSocket connection closed');
|
||||
}, 100);
|
||||
reject(new Error('WebSocket connection closed'));
|
||||
});
|
||||
});
|
||||
|
||||
// Event listener for WebSocket errors
|
||||
socket.addEventListener('error', (error) => {
|
||||
console.error('WebSocket error', error);
|
||||
reject(error); // Reject the promise on error
|
||||
});
|
||||
|
||||
// Event listener for WebSocket connection closure
|
||||
socket.addEventListener('close', () => {
|
||||
console.warn('WebSocket connection closed');
|
||||
reject(new Error('WebSocket connection closed')); // Reject with closure warning
|
||||
});
|
||||
});
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user