You've already forked fm-dx-webserver
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:
committed by
GitHub
parent
4ece21d513
commit
2ab7dd33dd
@@ -383,6 +383,24 @@ function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userC
|
|||||||
* WEBSOCKET BLOCK
|
* WEBSOCKET BLOCK
|
||||||
*/
|
*/
|
||||||
const tunerLockTracker = new WeakMap();
|
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) => {
|
wss.on('connection', (ws, request) => {
|
||||||
const output = serverConfig.xdrd.wirelessConnection ? client : serialport;
|
const output = serverConfig.xdrd.wirelessConnection ? client : serialport;
|
||||||
@@ -390,15 +408,44 @@ wss.on('connection', (ws, request) => {
|
|||||||
const userCommandHistory = {};
|
const userCommandHistory = {};
|
||||||
const normalizedClientIp = clientIp?.replace(/^::ffff:/, '');
|
const normalizedClientIp = clientIp?.replace(/^::ffff:/, '');
|
||||||
|
|
||||||
if (serverConfig.webserver.banlist?.includes(clientIp)) {
|
if (clientIp && serverConfig.webserver.banlist?.includes(clientIp)) {
|
||||||
ws.close(1008, 'Banned IP');
|
ws.close(1008, 'Banned IP');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientIp.includes(',')) {
|
if (clientIp && clientIp.includes(',')) {
|
||||||
clientIp = clientIp.split(',')[0].trim();
|
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() !== '')) {
|
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++;
|
currentUsers++;
|
||||||
}
|
}
|
||||||
@@ -472,6 +519,22 @@ wss.on('connection', (ws, request) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ws.on('close', (code, reason) => {
|
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() !== '')) {
|
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--;
|
currentUsers--;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ var parsedData, signalChart, previousFreq;
|
|||||||
var data = [];
|
var data = [];
|
||||||
var signalData = [];
|
var signalData = [];
|
||||||
let updateCounter = 0;
|
let updateCounter = 0;
|
||||||
|
let lastReconnectAttempt = 0;
|
||||||
let messageCounter = 0; // Count for WebSocket data length returning 0
|
let messageCounter = 0; // Count for WebSocket data length returning 0
|
||||||
let messageData = 800; // Initial value anything above 0
|
let messageData = 800; // Initial value anything above 0
|
||||||
let messageLength = 800; // Retain value of messageData until value is updated
|
let messageLength = 800; // Retain value of messageData until value is updated
|
||||||
@@ -375,10 +376,16 @@ function sendPingRequest() {
|
|||||||
messageCounter = 0;
|
messageCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatic reconnection on WebSocket close
|
// Automatic reconnection on WebSocket close with cooldown
|
||||||
if (socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) {
|
const now = Date.now();
|
||||||
|
if (
|
||||||
|
(socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) &&
|
||||||
|
(now - lastReconnectAttempt > TIMEOUT_DURATION)
|
||||||
|
) {
|
||||||
|
lastReconnectAttempt = now;
|
||||||
|
|
||||||
socket = new WebSocket(socketAddress);
|
socket = new WebSocket(socketAddress);
|
||||||
|
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
sendToast('info', 'Connected', 'Reconnected successfully!', false, false);
|
sendToast('info', 'Connected', 'Reconnected successfully!', false, false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
var url = new URL('text', window.location.href);
|
if (!window.socket || window.socket.readyState === WebSocket.CLOSED || window.socket.readyState === WebSocket.CLOSING) {
|
||||||
url.protocol = url.protocol.replace('http', 'ws');
|
var url = new URL('text', window.location.href);
|
||||||
var socketAddress = url.href;
|
url.protocol = url.protocol.replace('http', 'ws');
|
||||||
var socket = new WebSocket(socketAddress);
|
var socketAddress = url.href;
|
||||||
|
var socket = new WebSocket(socketAddress);
|
||||||
|
|
||||||
const socketPromise = new Promise((resolve, reject) => {
|
window.socket = socket;
|
||||||
// Event listener for when the WebSocket connection is open
|
|
||||||
socket.addEventListener('open', () => {
|
const socketPromise = new Promise((resolve, reject) => {
|
||||||
console.log('WebSocket connection open');
|
socket.addEventListener('open', () => {
|
||||||
resolve(socket); // Resolve the promise with the WebSocket instance
|
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
|
window.socketPromise = socketPromise;
|
||||||
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;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user