Allow SRV Lookups

This commit is contained in:
MCHost
2025-07-09 17:38:50 -04:00
parent 6761cbd960
commit f8632ade7e
3 changed files with 77 additions and 29 deletions

View File

@@ -1,6 +1,7 @@
const express = require('express');
const { promisify } = require('util');
const { exec } = require('child_process');
const dns = require('dns').promises; // Add DNS module for SRV lookup
const rateLimit = require('express-rate-limit');
const path = require('path');
const fs = require('fs');
@@ -31,13 +32,13 @@ const getClientIp = (req) => {
return req.headers['cf-connecting-ip'] || req.ip;
};
// Rate limiter configuration: 1 request per second with exponential backoff
// Rate limiter configuration: 10 requests per second with exponential backoff
const limiter = rateLimit({
windowMs: 1000, // 1 second
max: 10, // 1 request per IP
standardHeaders: true, // Return rate limit info in headers
legacyHeaders: false, // Disable X-RateLimit headers
keyGenerator: getClientIp, // Use CF-Connecting-IP or fallback to req.ip
max: 10, // 10 requests per IP
standardHeaders: true,
legacyHeaders: false,
keyGenerator: getClientIp,
handler: (req, res) => {
const retryAfter = Math.pow(2, Math.min(req.rateLimit.current - req.rateLimit.limit));
logger.warn('Rate limit exceeded', {
@@ -71,7 +72,6 @@ function validateBinaryPath(binaryPath) {
];
if (isDebug) logger.debug('Checking binary path against allowed prefixes', { resolvedPath, allowedPrefixes });
// Check if path exists
try {
fs.accessSync(resolvedPath, fs.constants.X_OK);
} catch (err) {
@@ -79,13 +79,11 @@ function validateBinaryPath(binaryPath) {
throw new Error('Binary path does not exist or is not executable');
}
// Check if path starts with an allowed prefix
if (!allowedPrefixes.some(prefix => resolvedPath.startsWith(prefix))) {
logger.error('Invalid binary path location', { resolvedPath, allowedPrefixes });
throw new Error('Invalid binary path');
}
// Check for shell metacharacters
if (/[|;&$><`]/.test(binaryPath)) {
logger.error('Invalid characters in binary path', { binaryPath });
throw new Error('Invalid characters in binary path');
@@ -134,13 +132,37 @@ function validateEdition(edition) {
return edition;
}
// SRV record lookup function
async function resolveSrv(hostname, edition) {
const srvDomain = `_minecraft._tcp.${hostname}`;
if (isDebug) logger.debug('Attempting SRV lookup', { srvDomain });
try {
const records = await dns.resolveSrv(srvDomain);
if (records.length > 0) {
const { name, port } = records[0];
if (isInfo) logger.info('SRV record found', { hostname, resolvedHost: name, resolvedPort: port });
return { host: name, port: port.toString() };
}
} catch (error) {
if (isDebug) logger.debug('No SRV record found or error during lookup', { srvDomain, error: error.message });
}
// Fallback to original hostname and port if no SRV record
return { host: hostname, port: null };
}
async function checkConnectionStatus(hostname, port) {
const requestId = Math.random().toString(36).substring(2);
if (isInfo) logger.info('Starting Java server status check', { requestId, hostname, port });
try {
// Resolve SRV record
const { host: resolvedHost, port: resolvedPort } = await resolveSrv(hostname, 'java');
const finalHost = resolvedHost || hostname;
const finalPort = resolvedPort || port;
const validatedBinary = validateBinaryPath(process.env.STATUS_CHECK_PATH);
const validatedHostname = validateHostname(hostname);
const validatedPort = validatePort(port);
const validatedHostname = validateHostname(finalHost);
const validatedPort = validatePort(finalPort);
const command = `${validatedBinary} -host ${validatedHostname} -port ${validatedPort}`;
if (isDebug) logger.debug('Executing command', { requestId, command });
@@ -151,7 +173,7 @@ async function checkConnectionStatus(hostname, port) {
return { isOnline: false, error: stderr };
}
const data = JSON.parse(stdout);
if (isInfo) logger.info('Java server status check completed', { requestId, isOnline: true });
if (isInfo) logger.info('Java server status check completed', { requestId, isOnline: true, resolvedHost: finalHost, resolvedPort: finalPort });
return { isOnline: true, data };
} catch (error) {
logger.error('Java server status check failed', { requestId, error: error.message });
@@ -162,10 +184,16 @@ async function checkConnectionStatus(hostname, port) {
async function checkGeyserStatus(hostname, port) {
const requestId = Math.random().toString(36).substring(2);
if (isInfo) logger.info('Starting Bedrock server status check', { requestId, hostname, port });
try {
// Resolve SRV record
const { host: resolvedHost, port: resolvedPort } = await resolveSrv(hostname, 'bedrock');
const finalHost = resolvedHost || hostname;
const finalPort = resolvedPort || port;
const validatedBinary = validateBinaryPath(process.env.GEYSER_STATUS_CHECK_PATH);
const validatedHostname = validateHostname(hostname);
const validatedPort = validatePort(port);
const validatedHostname = validateHostname(finalHost);
const validatedPort = validatePort(finalPort);
const command = `${validatedBinary} -host ${validatedHostname} -port ${validatedPort}`;
if (isDebug) logger.debug('Executing command', { requestId, command });
@@ -176,7 +204,7 @@ async function checkGeyserStatus(hostname, port) {
return { isOnline: false, error: stderr };
}
const data = JSON.parse(stdout);
if (isInfo) logger.info('Bedrock server status check completed', { requestId, isOnline: true });
if (isInfo) logger.info('Bedrock server status check completed', { requestId, isOnline: true, resolvedHost: finalHost, resolvedPort: finalPort });
return { isOnline: true, data };
} catch (error) {
logger.error('Bedrock server status check failed', { requestId, error: error.message });
@@ -286,7 +314,6 @@ const widgetTemplate = (edition, host, port) => `
const motdEl = document.getElementById('motd');
const statusEl = document.getElementById('status');
// Show loading state only on first load
if (isFirstLoad) {
loadingSpinner.classList.add('visible');
versionEl.classList.remove('visible');
@@ -305,7 +332,6 @@ const widgetTemplate = (edition, host, port) => `
}
const result = await response.json();
// Hide loading spinner
loadingSpinner.classList.remove('visible');
if (result.isOnline) {
@@ -340,13 +366,10 @@ const widgetTemplate = (edition, host, port) => `
statusEl.classList.add('visible');
}
// Update first load flag
isFirstLoad = false;
}
// Initial update
updateStatus();
// Update every 30 seconds
setInterval(updateStatus, 30000);
</script>
</body>