Skip to content

Fix: Filter out localhost/loopback IPs from device discovery#61

Open
sandinak wants to merge 4 commits into
featherbear:masterfrom
sandinak:fix/filter-localhost-discovery
Open

Fix: Filter out localhost/loopback IPs from device discovery#61
sandinak wants to merge 4 commits into
featherbear:masterfrom
sandinak:fix/filter-localhost-discovery

Conversation

@sandinak

@sandinak sandinak commented Feb 19, 2026

Copy link
Copy Markdown

Problem

The discovery service had multiple issues:

  1. Incorrect IP Reporting: Discovery was returning the local machine's IP (192.168.20.211) instead of the actual mixer's IP address when receiving UDP broadcast packets, causing connection failures.

  2. Missing Device Information: The UDP packet contains both the model name (e.g., "StudioLive 16R") and user-assigned device name (e.g., "Office"), but only the model was being captured.

  3. Security Vulnerabilities: 6 high severity npm vulnerabilities in dependencies.

  4. Limited Debugging: No logging to help diagnose discovery reliability issues.

Root Cause

When listening for UDP broadcasts on port 47809, the discovery service receives packets from both the actual mixer and local processes. The code was not filtering out local addresses, causing rinfo.address to sometimes return the host IP instead of the actual mixer's IP address.

Solution

Commit 1: Fix Local IP Filtering

Added filtering logic to skip local machine IP addresses in the discovery message handler:

  • Added getLocalIPs() helper function using networkInterfaces() from node:os to enumerate all local IPs (IPv4 and IPv6)
  • Added isLocalIP() helper function to detect local machine addresses including loopback ranges
  • Early return in message handler to skip packets from local IPs
  • Ensures only actual network device IPs are emitted in the 'discover' event

Commit 2: Fix Security Vulnerabilities

Resolved all npm security issues:

  • Replaced unmaintained npm-run-all with npm-run-all2 (maintained fork)
  • Updated @swc/cli and @swc/core to latest versions
  • Added overrides section in package.json to force secure versions of transitive dependencies
  • Result: 0 vulnerabilities (was 6 high severity)

Commit 3: Enhanced Device Information & Debugging

Improved discovery data and added debugging capabilities:

  • Updated DiscoveryType interface to include:
    • model: Device model name (e.g., "StudioLive 16R")
    • deviceName: User-assigned device name (e.g., "Office")
    • name: Kept for backward compatibility (same as model)
  • Updated Discovery.ts to parse both names from UDP packet fragments
  • Added console logging:
    • Logs filtered local IPs on startup
    • Logs when discovery starts listening on port 47809
    • Logs each discovered device: [Discovery] Found device: StudioLive 16R (Office) at 192.168.21.41 [RA1E24090034]

Testing

  • ✅ Built successfully with npm run build - no TypeScript errors
  • npm audit shows 0 vulnerabilities
  • ✅ Tested with real StudioLive 16R mixer - correctly discovers mixer IP and both names
  • ✅ Local IPs (192.168.20.211) are properly filtered out
  • ✅ Discovery logs show: Found device: StudioLive 16R (Office) at 192.168.21.41 [RA1E24090034]

Impact

  • Reliability: Users will now reliably get the correct mixer IP address during discovery
  • Security: All npm vulnerabilities resolved
  • Usability: Applications can now display both model and custom device names
  • Debugging: Console logs help diagnose discovery timing and reliability issues
  • Backward Compatibility: Existing code using name field continues to work

Files Changed

  • src/lib/Discovery.ts - Local IP filtering and device name parsing
  • src/lib/types/DiscoveryType.ts - Added model and deviceName fields
  • package.json - Updated dependencies and added overrides
  • package-lock.json - Locked secure dependency versions

Pull Request opened by Augment Code with guidance from the PR author

The discovery service was incorrectly reporting the local machine's
network IP (e.g., 192.168.20.211) as the mixer IP address when
receiving UDP broadcast packets. This happened because the local
machine's own broadcast packets were being captured and reported
as discovered devices.

Root Cause:
When listening for UDP broadcasts on port 47809, the discovery
service receives packets from both the actual mixer AND the local
machine's own network interfaces. The code was not filtering out
the local machine's IP addresses, causing rinfo.address to return
the local IP instead of the mixer's IP.

Solution:
- Added getLocalIPs() function to enumerate all local network interfaces
- Added isLocalIP() function to check if an IP belongs to local machine
- Filter out all local IPs (including 192.168.x.x, 10.x.x.x, etc.)
- Filter out loopback addresses (127.0.0.0/8)
- Early return in message handler to skip local discoveries

Changes:
- Import networkInterfaces from node:os
- Added getLocalIPs() helper (lines 12-3- Added getLocalIPs() helper (lines 12-3- Added getLocalIPs() helpep() (line 92)
- Filter check in message handler (lines 105-108)

TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTemitted in the 'discover' event, preventing false positives from
the local machine's own UDP broadcasts.
@sandinak sandinak force-pushed the fix/filter-localhost-discovery branch from de4d63e to 8602a9c Compare February 19, 2026 12:30
@sandinak

Copy link
Copy Markdown
Author

Update: Corrected the Fix

I've updated this PR with the correct fix. The issue was not about localhost (127.0.0.1), but rather about the local machine's network IP being reported.

Actual Problem

When the discovery service listens for UDP broadcasts, it receives packets from:

  1. The actual mixer (e.g., 192.168.20.100)
  2. The local machine's own network interfaces (e.g., 192.168.20.211)

The code was reporting the local machine's IP (192.168.20.211) as the "discovered" mixer, which caused connection failures.

Updated Solution

The fix now:

  • Uses networkInterfaces() from node:os to enumerate all local network interfaces
  • Filters out any IP address that belongs to the local machine (not just 127.x.x.x)
  • This includes private network IPs like 192.168.x.x, 10.x.x.x, 172.16.x.x, etc.

Code Changes

Added:

  • getLocalIPs() - Enumerates all local network interface IPs
  • isLocalIP(ip, localIPs) - Checks if an IP belongs to the local machine

Modified:

  • Discovery setup now gets local IPs once at startup
  • Message handler filters out any packets from local IPs

Result

Now only remote device IPs (like the actual mixer) will be emitted in discovery events. The local machine's own broadcast packets are correctly filtered out.

Testing

✅ Build successful with npm run build
✅ No TypeScript errors
✅ Correctly filters local machine IP (192.168.20.211)
✅ Only reports actual mixer IP

Branson Matheson and others added 3 commits February 19, 2026 07:39
Fixed 6 high severity vulnerabilities in dependencies:

Changes:
- Replaced npm-run-all with npm-run-all2 (maintained fork)
- Updated @swc/cli and @swc/core to latest versions
- Added overrides section to force secure versions of:
  - minimatch ^10.2.1 (was <10.2.1, had ReDoS vulnerability)
  - glob ^11.0.0 (was using old versions with vulnerable minimatch)
  - rimraf ^6.0.1 (was using old versions with vulnerable glob)

Result: npm audit now shows 0 vulnerabilities
Build tested and working correctly
Enhancements:
- Added 'model' and 'deviceName' fields to DiscoveryType
  - model: Device model (e.g., 'StudioLive 16R')
  - deviceName: User-assigned name (e.g., 'Office')
  - name: Kept for backward compatibility (same as model)

- Added console logging for better debugging:
  - Log filtered local IPs on startup
  - Log when discovery starts listening
  - Log each discovered device with model, name, IP, and serial

This helps diagnose discovery reliability issues and provides
more useful device information to applications.
Discovery:
- Source-port filter: reject packets not from port 47809, preventing
  other software (DAW plugins, Universal Control, other app instances)
  from appearing as mixers
- Refresh localIPs on every packet so VPN/dynamic interface changes
  don't produce stale filter entries

API cleanup:
- Remove debug console.log statements from simple/index.ts, packetParsers,
  messageProtocol, zlibUtil, and zlibNodeParser
- Remove MeterServer.ts debug logging
- Client.ts: remove unused debug instrumentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@featherbear

featherbear commented May 15, 2026

Copy link
Copy Markdown
Owner

Do you have examples of local processes emitting discovery packets, where they override the reported addresses?

These LOCs should be correct in functionality

ip: rinfo.address,
port: rinfo.port,

rinfo.port docs is the sender port. From my packet captures, consoles emit from 53000/udp. Seems to conflict with https://github.com/featherbear/presonus-studiolive-api/pull/61/changes#diff-f6d3dab013a719eedeb31258d153c674d3ad9bb891db9b3fb102bd6409cda8d3R107

--

I can't verify the behaviour for the CS18AI as I don't own one, but I'll check out what the NSBs look like when on the network

Comment thread src/lib/Discovery.ts
// This rejects any other software sending UCNet-compatible packets from
// a different source port (e.g. another instance of this app, DAW
// plugins, or Universal Control running on the same machine).
if (rinfo.port !== 47809) return;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@featherbear featherbear Jun 1, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the AVB network, the NSB 16.8 and NSB 8.8 emit from 49153/udp to (broadcast) 47809/udp
My packet capture didn't pick up the console broadcast on that network. Would have to look at the usual control network

Comment thread src/lib/Discovery.ts
// The model name must look like a real PreSonus device. Known prefixes:
// "StudioLive", "CS18AI", "NSB". Reject anything that doesn't match to
// prevent other instances of this app from appearing as mixers.
const knownModelPrefix = /^(StudioLive|CS18AI|NSB)/i;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/lib/Discovery.ts
const discoveryData = {
name: model, // Keep 'name' for backward compatibility
model,
deviceName: deviceName || undefined,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The array destructure already assigns deviceName to undefined, no real need to provide a fallback value of undefined

Comment thread src/lib/Discovery.ts

// eslint-disable-next-line
const [nameA, _, serial, nameB] = fragments;
const [model, _, serial, deviceName] = fragments;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is welcomed info about the data structure, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants