From 11c3a405735ff555ba254f340251a4b09077b0c9 Mon Sep 17 00:00:00 2001 From: Alan Hargreaves Date: Sat, 21 Mar 2026 00:43:45 +1100 Subject: [PATCH 01/13] make the relaytake an option (--multicast or -m) that i ssupplied with a multicast address that will make it listen on a multicast udp rather than a unicast. --- wsjtx-relay/relay.js | 49 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/wsjtx-relay/relay.js b/wsjtx-relay/relay.js index 870ed9aa..bf701a04 100644 --- a/wsjtx-relay/relay.js +++ b/wsjtx-relay/relay.js @@ -11,10 +11,12 @@ * Zero dependencies — uses only Node.js built-in modules. * * Usage: - * node relay.js --url https://openhamclock.com --key YOUR_RELAY_KEY + * node relay.js --url https://openhamclock.com --key YOUR_RELAY_KEY [--multicast address] + * where the multicast address is something like 224.0.0.1 * * Or with environment variables: * OPENHAMCLOCK_URL=https://openhamclock.com RELAY_KEY=abc123 node relay.js + * MULTICAST=224.0.0.1 * * In WSJT-X: Settings → Reporting → UDP Server * Address: 127.0.0.1 Port: 2237 @@ -33,6 +35,7 @@ function parseArgs() { const args = process.argv.slice(2); const config = { url: process.env.OPENHAMCLOCK_URL || '', + maddr: process.env.MULTICAST || '', key: process.env.RELAY_KEY || process.env.OPENHAMCLOCK_RELAY_KEY || '', session: process.env.RELAY_SESSION || '', port: parseInt(process.env.WSJTX_UDP_PORT || '2237'), @@ -50,6 +53,10 @@ function parseArgs() { case '-k': config.key = args[++i]; break; + case '--multicast': + case '-m': + config.maddr = args[++i]; + break; case '--session': case '-s': config.session = args[++i]; @@ -77,6 +84,9 @@ a remote OpenHamClock server. Options: --url, -u OpenHamClock server URL (required) --key, -k Relay authentication key (required) + --multicast, -m
+ Bind to the specified multicast address rather than + a non-multicast socket --session, -s Browser session ID (required for per-user isolation) --port, -p Local UDP port to listen on (default: 2237) --interval, -i Batch send interval in ms (default: 2000) @@ -86,6 +96,7 @@ Options: Environment variables: OPENHAMCLOCK_URL Same as --url RELAY_KEY Same as --key + MULTICAST Same as --multicast RELAY_SESSION Same as --session WSJTX_UDP_PORT Same as --port BATCH_INTERVAL Same as --interval @@ -456,7 +467,7 @@ function scheduleBatch() { // UDP LISTENER // ============================================ -const socket = dgram.createSocket('udp4'); +const socket = dgram.createSocket({ type: 'udp4', reuseAddr: true }); socket.on('message', (buf, rinfo) => { const msg = parseWSJTXMessage(buf); @@ -504,6 +515,18 @@ socket.on('listening', () => { console.log(' Waiting for WSJT-X packets...'); console.log(''); + // Bind to all interfaces so WSJT-X can reach it from any address + // socket.bind(config.port, '0.0.0.0'); + + if (!!config.maddr) { + try { + socket.addMembership(config.maddr); + console.log(`[WSJT-X] Joined multicast group ${config.maddr}`); + } catch (e) { + console.error(`[WSJT-X] Failed to join multicast group ${config.maddr}: ${e.message}`); + } + } + // Start batch relay loop scheduleBatch(); @@ -623,8 +646,26 @@ socket.on('listening', () => { }, 60000); // every minute }); -// Bind to all interfaces so WSJT-X can reach it from any address -socket.bind(config.port, '0.0.0.0'); +if (!!config.maddr) { + // Bind to 0.0.0.0 explicitly — on some Linux systems (especially Pi) omitting + // the address can cause the socket to bind to the wrong interface, preventing + // multicast group membership from working. + socket.bind( + { + port: config.port, + address: '0.0.0.0', + exclusive: false, + }, + () => { + socket.setMulticastLoopback(true); + }, + ); +} else { + socket.bind({ + port: config.port, + address: '0.0.0.0', + }); +} // ============================================ // GRACEFUL SHUTDOWN From 5158db735d15b71856f6ffebb7531c5ef19124b3 Mon Sep 17 00:00:00 2001 From: Alan Hargreaves Date: Sun, 22 Mar 2026 19:17:57 +1100 Subject: [PATCH 02/13] - Handle being passed a multicast address for relay download - modified the startup script for the relay to not use mktemp on macos as it behaves differenty to that on Linux --- server/routes/wsjtx.js | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/server/routes/wsjtx.js b/server/routes/wsjtx.js index 18a60536..81bfbd0e 100644 --- a/server/routes/wsjtx.js +++ b/server/routes/wsjtx.js @@ -1119,6 +1119,8 @@ module.exports = function (app, ctx) { error: 'Session ID required — download from the OpenHamClock dashboard', }); } + // Get the multicast address if we were passed one + const multicastAddress = req.query.multicast; // SECURITY: Validate platform parameter if (!['linux', 'mac', 'windows'].includes(platform)) { @@ -1133,6 +1135,7 @@ module.exports = function (app, ctx) { const safeServerURL = sanitizeForShell(serverURL); const safeSessionId = sanitizeForShell(sessionId); const safeRelayKey = sanitizeForShell(WSJTX_RELAY_KEY); + const safeMulticastAddress = multicastAddress ? sanitizeForShell(multicastAddress) : ''; if (platform === 'linux' || platform === 'mac') { // Build bash script with relay.js embedded as heredoc @@ -1164,11 +1167,11 @@ module.exports = function (app, ctx) { ' exit 1', 'fi', '', - '# Write relay agent to temp file', - 'RELAY_FILE=$(mktemp /tmp/ohc-relay-XXXXXX.js)', + '# Write relay agent to temp file (unfortunately mktemp on macOS does not work the same as on Linux', + 'RELAY_FILE=$([[ "$(uname -s)" == Linux ]] && mktemp /tmp/ohc-relay-XXXXXX.js || echo /tmp/ohc-relay-$$.js)', 'trap "rm -f $RELAY_FILE" EXIT', '', - 'cat > "$RELAY_FILE" << \'OPENHAMCLOCK_RELAY_EOF\'', + 'cat > "${RELAY_FILE}" << \'OPENHAMCLOCK_RELAY_EOF\'', relayJs, 'OPENHAMCLOCK_RELAY_EOF', '', @@ -1176,6 +1179,7 @@ module.exports = function (app, ctx) { 'exec node "$RELAY_FILE" \\', ' --url "' + safeServerURL + '" \\', ' --key "' + safeRelayKey + '" \\', + multicastAddress ? ' --multicast "' + safeMulticastAddress + '" \\' : '', ' --session "' + safeSessionId + '"', ]; @@ -1276,19 +1280,22 @@ module.exports = function (app, ctx) { 'echo Relay agent ready.', 'echo.', 'echo In WSJT-X: Settings ^> Reporting ^> UDP Server', - 'echo Address: 127.0.0.1 Port: 2237', + 'echo Address: ' + multicastAddress ? safeMulticastAddress : '127.0.0.1' + ' Port: 2237,', 'echo.', 'echo Press Ctrl+C to stop', 'echo.', '', ':: Run relay', '%NODE_EXE% "%TEMP%\\ohc-relay.js" --url "' + - safeServerURL + - '" --key "' + - safeRelayKey + - '" --session "' + - safeSessionId + - '"', + safeServerURL + + '" --key "' + + safeRelayKey + + '" --session "' + + safeSessionId + + '"' + + multicastAddress + ? ' --multicast "' + safeMulticastAddress + "'" + : '', '', 'echo.', 'echo Relay stopped.', From fbf94447e578e0da4e51b54be560b6f0fc10b67f Mon Sep 17 00:00:00 2001 From: Alan Hargreaves Date: Sun, 22 Mar 2026 19:20:35 +1100 Subject: [PATCH 03/13] Add default configuration for wsjtxRelayMulticast --- src/utils/config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/config.js b/src/utils/config.js index a25cb810..aaf0f01b 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -64,6 +64,7 @@ export const DEFAULT_CONFIG = { dxClusterSource: 'dxspider-proxy', customDxCluster: { enabled: false, host: '', port: 7300 }, udpDxCluster: { host: '', port: 12060 }, + wsjtxRelayMulticast: { enabled: false, address: '224.0.0.1' }, }; // Cache for server config From 75b8657a8847ef86dd4a6d1133f0280bb6f10fd5 Mon Sep 17 00:00:00 2001 From: Alan Hargreaves Date: Sun, 22 Mar 2026 19:22:15 +1100 Subject: [PATCH 04/13] Add configuration options for wsjtxRelayMulticast --- src/components/SettingsPanel.jsx | 50 +++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/components/SettingsPanel.jsx b/src/components/SettingsPanel.jsx index 07927ea3..046cfa0d 100644 --- a/src/components/SettingsPanel.jsx +++ b/src/components/SettingsPanel.jsx @@ -77,6 +77,10 @@ export const SettingsPanel = ({ return false; } }); + const [wsjtxMulticastEnabled, setWsjtxMulticastEnabled] = useState(config?.wsjtxRelayMulticast.enabled || false); + const [wsjtxMulticastAddress, setWsjtxMulticastAddress] = useState( + config?.wsjtxRelayMulticast.address || '224.0.0.1', + ); // Local-only integration flags const [n3fjpEnabled, setN3fjpEnabled] = useState(() => { @@ -418,7 +422,7 @@ export const SettingsPanel = ({ // units, allUnits: { dist: distUnits, temp: tempUnits, press: pressUnits }, propagation: { mode: propMode, power: parseFloat(propPower) || 100 }, - + wsjtxRelayMulticast: { enabled: wsjtxMulticastEnabled, address: wsjtxMulticastAddress }, rigControl: { enabled: rigEnabled, host: rigHost, @@ -1065,6 +1069,50 @@ export const SettingsPanel = ({ + {/* WSJTX Relay Multicast Options */} +
+ +
+ setWsjtxMulticastEnabled(e.target.checked)} + style={{ marginRight: '8px' }} + /> + Use multicast address   + setWsjtxMulticastAddress(e.target.value.toUpperCase())} + style={{ + width: '10%', + marginLeft: '8px', + padding: '8px 12px', + background: 'var(--bg-primary)', + border: '1px solid var(--border-color)', + borderRadius: '4px', + color: wsjtxMulticastEnabled ? 'var(--text-primary)' : 'var(--text-secondary', + fontSize: '12px', + fontFamily: 'JetBrains Mono, monospace', + boxSizing: 'border-box', + }} + /> + If you are going to run a wsjt-x relay, define here if you need a multicast listener and what address it + should be using. +
+
+ {/* Rig Control Settings */}