Skip to content

[Refactor] Network Improvements - #244

Open
mpotrykus wants to merge 3 commits into
TheElixZammuto:masterfrom
mpotrykus:mdns-improvements
Open

[Refactor] Network Improvements#244
mpotrykus wants to merge 3 commits into
TheElixZammuto:masterfrom
mpotrykus:mdns-improvements

Conversation

@mpotrykus

@mpotrykus mpotrykus commented Jan 9, 2026

Copy link
Copy Markdown
Contributor

Refactored the MDNSHandler and general network-handling.

Most notable improvements are:

  • Hosts will now be discovered without refreshing the page
  • Support for IPv6
  • Port priority (will silently update the port if a more-preferred option becomes available)
  • App will gracefully teardown and re-init mdns on suspend/resume
  • New NetworkUtils library

Tested both IPv4 & IPv6:

  • HostSelectorPage - NavigateTo (general host discovery)
  • "Add Host"
  • WakeOnLan

Summary by CodeRabbit

  • New Features

    • Automatic discovery of Moonlight hosts on the local network via mDNS.
    • Improved IP selection for hosts (better IPv4/IPv6 prioritization).
    • Persisting mDNS instance identifiers for hosts and automatic address updates/deduplication.
    • App capability updated to allow server-style network access for discovery.
  • Bug Fixes / Reliability

    • More robust mDNS lifecycle handling across suspend/resume and network reinitialization.

@coderabbitai

coderabbitai Bot commented Jan 9, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c7ffd2 and 6dcdf9b.

📒 Files selected for processing (2)
  • App.xaml.cpp
  • moonlight-xbox-dx.vcxproj
🚧 Files skipped from review as they are similar to previous changes (2)
  • App.xaml.cpp
  • moonlight-xbox-dx.vcxproj

📝 Walkthrough

Walkthrough

Adds mDNS lifecycle control and robust mDNS resolution, a NetworkUtils module for IP parsing/selection, persists mdns_instance_name in host state with deduplication, and wires suspend/resume plus navigation hooks to teardown/reinitialize MDNS.

Changes

Cohort / File(s) Summary
App lifecycle & Manifest
App.xaml.cpp, Package.appxmanifest
Call close_mdns() on suspend and reinit_mdns() on resume; add internetClientServer capability to manifest.
Network utilities
Utils/NetworkUtils.h, Utils/NetworkUtils.cpp
New NetworkUtils namespace: IP extraction, IPv4/IPv6 checks, IP priority/picking, SplitIPAddress, and GetBroadcastIP implementations.
Host state & model
State/ApplicationState.h, State/ApplicationState.cpp, State/MoonlightHost.h
Add mdnsInstanceName property to host; change AddHost signature to accept mdnsInstanceName; add UpdateHostAddressForInstance; persist mdns_instance_name; host deduplication and non-regressive address upgrades.
MDNS subsystem
State/MDNSHandler.h, State/MDNSHandler.cpp
Add close_mdns() and reinit_mdns(); rewrite MDNS resolution: PTR/SRV/A/AAAA aggregation, compressed-name parsing, per-socket non-blocking query/recv, and internal instance/host/ip mappings.
UI / Host selection
Pages/HostSelectorPage.xaml.cpp
Invoke init_mdns() on navigation; use NetworkUtils::SplitIPAddress and pass nullptr for mdnsInstanceName when adding manual hosts; add fallback parsing.
Build files
moonlight-xbox-dx.vcxproj, moonlight-xbox-dx.vcxproj.filters
Add Utils/NetworkUtils.h and Utils/NetworkUtils.cpp to project and filters.

Sequence Diagram(s)

sequenceDiagram
    participant App as App Lifecycle
    participant MDNS as MDNSHandler
    participant State as ApplicationState

    App->>MDNS: close_mdns() on suspend
    MDNS->>MDNS: close sockets, cleanup state
    App->>MDNS: reinit_mdns() on resume
    MDNS->>MDNS: reopen sockets, enable multicast, broadcast PTR
    MDNS->>State: UpdateHostAddressForInstance(mdnsInstanceName, host:port)
    State->>State: deduplicate & persist host (mdns_instance_name)
Loading
sequenceDiagram
    participant Page as HostSelectorPage
    participant Utils as NetworkUtils
    participant MDNS as MDNSHandler
    participant State as ApplicationState

    Page->>MDNS: init_mdns() on navigate
    MDNS->>MDNS: send PTR/SRV queries, receive A/AAAA
    MDNS->>MDNS: map instance->host, host->ips
    MDNS->>State: UpdateHostAddressForInstance(mdnsInstanceName, addr)
    State->>Utils: SplitIPAddress(host:port)
    Utils->>State: return ip and port
    State->>State: update/persist host record
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #247: Reintroduces/init MDNS on HostSelectorPage navigation — directly related to MDNS lifecycle changes.
  • #175: Refactors IP-splitting and address parsing used by HostSelectorPage and ApplicationState — overlaps NetworkUtils work.
  • #227: Modifies HostSelectorPage host-management flows that now interact with mdnsInstanceName and ApplicationState updates.

Suggested reviewers

  • TheElixZammuto

Poem

🐰 Soft paws on packets, I hop and find,
MDNS whispers names across the rind.
Suspend, resume — I tidy, then sing,
Hosts deduped and IPs I bring.
Hooray for networks — a rabbit's tiny ping! 🐇✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title '[Refactor] Network Improvements' is vague and overly broad, using generic terms that don't convey the specific main changes; it doesn't clearly highlight the primary improvements like mDNS refactoring, IPv6 support, or host discovery. Consider a more specific title such as 'Add mDNS refactoring with IPv6 support and automatic host discovery' or 'Implement mDNS improvements and NetworkUtils library for better host discovery'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Pages/HostSelectorPage.xaml.cpp (1)

270-278: Potential socket leak on repeated page navigation.

init_mdns() is called each time OnNavigatedTo fires, but sockets from a previous navigation are never closed (the page doesn't call close_mdns() on exit). The static sockets[] array will be overwritten, leaking previously opened sockets.

Consider calling close_mdns() in OnNavigatedFrom, or use reinit_mdns() here instead of init_mdns() to ensure stale sockets are cleaned up before opening new ones.

Suggested fix: use reinit_mdns() or add cleanup
 void HostSelectorPage::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) {
 	Windows::UI::ViewManagement::ApplicationView::GetForCurrentView()->SetDesiredBoundsMode(Windows::UI::ViewManagement::ApplicationViewBoundsMode::UseVisible);
 	continueFetch.store(true);
 	Concurrency::create_task([this] {
-		init_mdns();
+		reinit_mdns();  // Ensures previous sockets are closed before opening new ones
 		while (continueFetch.load()) {

Or add an OnNavigatedFrom handler:

void HostSelectorPage::OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) {
    continueFetch.store(false);
    close_mdns();
}
🤖 Fix all issues with AI agents
In @App.xaml.cpp:
- Around line 105-108: Currently exceptions from close_mdns() in App.xaml.cpp
are swallowed, hiding teardown errors; update the try/catch to catch
std::exception and any other exceptions and log them via the existing logging
facility (e.g., processLogger or app logger) including the exception.what() for
std::exception and a generic message for unknown exceptions, so change the empty
catch(...) to one that logs the error and retains the swallow behavior to avoid
suspend failures while surfacing diagnostics for close_mdns().
- Around line 121-124: The try/catch around reinit_mdns() currently swallows all
exceptions; update it to catch std::exception and a fallback catch(...) and log
the failure so mDNS reinit errors are visible—call the project's logging
facility (or OutputDebug/Trace) from the catch(std::exception& e) with a message
like "reinit_mdns failed: " + e.what(), and in catch(...) log an "unknown
exception in reinit_mdns" message; keep calling reinit_mdns() in the try block
and ensure the logs include enough context to diagnose resume/discovery
failures.

In @State/ApplicationState.cpp:
- Around line 348-363: Wrap the calls to NetworkUtils::SplitIPAddress and
NetworkUtils::GetBroadcastIP in try-catch blocks to prevent exceptions from
propagating when LastHostname or ServerAddress are malformed; catch
std::invalid_argument and std::out_of_range (or std::exception as a fallback),
log an informative message via Utils::Log (including the offending
LastHostname/ServerAddress) and skip pushing results into the addresses vector
so the WoL flow continues safely. Ensure the try-catch surrounds the
SplitIPAddress call where host->LastHostname is used and the GetBroadcastIP call
where host->ServerAddress is used, and do not rethrow the exceptions.
- Around line 102-115: The race occurs because UpdateFile() is called
immediately after dispatching the UI thread lambda, so SavedHosts->Append(host)
(inside the RunAsync lambda) may not have run yet; fix by invoking UpdateFile()
from inside the dispatched handler immediately after SavedHosts->Append(host)
(i.e., move the UpdateFile() call into the lambda that runs in
Windows::UI::Core::DispatchedHandler) so the file update happens only after the
host is appended (alternatively capture the IAsyncAction returned by RunAsync
and wait for completion before calling UpdateFile(), but the simplest fix is to
call UpdateFile() right after SavedHosts->Append(host) within the dispatched
callback).
- Line 186: hostJson["mdns_instance_name"] is set using
Utils::PlatformStringToStdString(host->MdnsInstanceName) without a nullptr
check; change this to guard against host->MdnsInstanceName being nullptr (the
same pattern used elsewhere) by using a conditional expression so that if
host->MdnsInstanceName is nullptr you assign an empty string, otherwise call
Utils::PlatformStringToStdString(host->MdnsInstanceName), keeping the assignment
to hostJson["mdns_instance_name"] unchanged.

In @State/MDNSHandler.cpp:
- Around line 27-34: In ipv4_address_to_string, the local variable named
"service" shadows the file-scope "service" constant; rename the local to
something distinct (e.g., service_buf or service_local) and update all uses
within ipv4_address_to_string (including the getnameinfo call and any subsequent
references) to avoid the shadowing while preserving the buffer size NI_MAXSERV.

In @Utils/NetworkUtils.cpp:
- Around line 154-157: The GetBroadcastIP function incorrectly calls
WSACleanup() on its error path, which will decrement Winsock's reference count
even though GetBroadcastIP does not call WSAStartup(); remove the errant
WSACleanup() invocation from GetBroadcastIP (and any similar early-return error
branches) so Winsock cleanup is only performed by the code that called
WSAStartup() or a dedicated shutdown routine; if necessary, ensure any
WSAStartup() caller is responsible for a matching WSACleanup() instead of doing
cleanup inside GetBroadcastIP.
🧹 Nitpick comments (3)
State/MDNSHandler.cpp (1)

152-216: Consider using NetworkUtils to reduce duplication.

These static IP classification and priority functions (is_ipv4, is_private_ipv4, ip_priority, pick_best_ip) duplicate the logic in NetworkUtils. Consider including NetworkUtils.h and calling the shared implementations to maintain consistency and reduce maintenance burden.

Utils/NetworkUtils.h (1)

1-25: LGTM!

Clean header with well-organized utility function declarations. The API provides comprehensive IP address classification and manipulation capabilities.

Minor nit: Line 23 uses different indentation than the rest of the file.

Utils/NetworkUtils.cpp (1)

8-8: Unused include: nlohmann/json.hpp.

This header is included but not used anywhere in the file. Remove it to reduce compilation time and dependencies.

-#include <nlohmann/json.hpp>
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3993f9a and 2c7ffd2.

📒 Files selected for processing (12)
  • App.xaml.cpp
  • Package.appxmanifest
  • Pages/HostSelectorPage.xaml.cpp
  • State/ApplicationState.cpp
  • State/ApplicationState.h
  • State/MDNSHandler.cpp
  • State/MDNSHandler.h
  • State/MoonlightHost.h
  • Utils/NetworkUtils.cpp
  • Utils/NetworkUtils.h
  • moonlight-xbox-dx.vcxproj
  • moonlight-xbox-dx.vcxproj.filters
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2026-01-08T15:15:34.393Z
Learnt from: mpotrykus
Repo: TheElixZammuto/moonlight-xbox PR: 243
File: moonlight-xbox-dx.vcxproj:661-663
Timestamp: 2026-01-08T15:15:34.393Z
Learning: In the moonlight-xbox project (moonlight-xbox-dx.vcxproj), the ProjectReference to third_party\DirectXTK\DirectXTK_Windows10_2022.vcxproj is intentionally added before the file exists. The setup-dev.ps1 script generates this project file during setup, and the reference ensures the correct build order (DirectXTK before main project) once everything is generated, avoiding manual build requirements.

Applied to files:

  • moonlight-xbox-dx.vcxproj.filters
  • moonlight-xbox-dx.vcxproj
📚 Learning: 2025-02-27T02:36:15.388Z
Learnt from: LAGonauta
Repo: TheElixZammuto/moonlight-xbox PR: 167
File: State/MoonlightHost.h:38-38
Timestamp: 2025-02-27T02:36:15.388Z
Learning: In MoonlightHost class, hdmiDisplayMode should be explicitly initialized to nullptr in the constructor, and null checks should be added before accessing it in methods like Connect(). This is particularly important because hdmiDisplayMode might not be initialized when loading previously saved hosts that don't contain display information.

Applied to files:

  • State/ApplicationState.cpp
📚 Learning: 2025-02-27T02:36:15.388Z
Learnt from: LAGonauta
Repo: TheElixZammuto/moonlight-xbox PR: 167
File: State/MoonlightHost.h:38-38
Timestamp: 2025-02-27T02:36:15.388Z
Learning: When using hdmiDisplayMode in the MoonlightHost::Connect() method, a null check should be performed since hdmiDisplayMode might be null when loading previously saved hosts without display information. The MoonlightClient constructor should either handle null parameters or the Connect() method should initialize hdmiDisplayMode with a default value before passing it.

Applied to files:

  • State/ApplicationState.cpp
📚 Learning: 2025-02-12T16:34:25.238Z
Learnt from: mpotrykus
Repo: TheElixZammuto/moonlight-xbox PR: 163
File: State/ApplicationState.cpp:0-0
Timestamp: 2025-02-12T16:34:25.238Z
Learning: Wake-on-LAN magic packets must be sent to either port 7 or port 9 on the destination host as per the protocol specification. The source port can be ephemeral but the destination port must be either 7 or 9.

Applied to files:

  • State/ApplicationState.cpp
🧬 Code graph analysis (5)
State/MDNSHandler.h (1)
State/MDNSHandler.cpp (6)
  • query_mdns (417-472)
  • query_mdns (417-417)
  • close_mdns (474-483)
  • close_mdns (474-474)
  • reinit_mdns (485-492)
  • reinit_mdns (485-485)
Utils/NetworkUtils.h (1)
Utils/NetworkUtils.cpp (22)
  • ExtractIpFromHostPort (12-30)
  • ExtractIpFromHostPort (12-12)
  • IsIpv4 (32-34)
  • IsIpv4 (32-32)
  • IsIpv6 (36-38)
  • IsIpv6 (36-36)
  • IsPrivateIpv4 (40-54)
  • IsPrivateIpv4 (40-40)
  • IsIpv6Ula (56-61)
  • IsIpv6Ula (56-56)
  • IsIpv6Gua (67-71)
  • IsIpv6Gua (67-67)
  • IsIpv6LinkLocal (63-65)
  • IsIpv6LinkLocal (63-63)
  • IpPriority (74-88)
  • IpPriority (74-74)
  • PickBestIp (90-103)
  • PickBestIp (90-90)
  • SplitIPAddress (105-123)
  • SplitIPAddress (105-105)
  • GetBroadcastIP (125-170)
  • GetBroadcastIP (125-125)
State/ApplicationState.cpp (4)
Common/ModalDialog.xaml.cpp (16)
  • a (157-159)
  • a (159-161)
  • a (163-165)
  • a (165-167)
  • a (167-169)
  • a (205-205)
  • a (221-221)
  • a (226-228)
  • a (228-230)
  • a (261-261)
  • a (277-277)
  • a (282-284)
  • a (284-286)
  • a (296-296)
  • a (360-362)
  • a (362-364)
Utils.cpp (6)
  • Logf (90-99)
  • Logf (90-90)
  • Log (57-82)
  • Log (57-57)
  • Log (84-88)
  • Log (84-84)
Utils.hpp (3)
  • Logf (15-15)
  • Log (13-13)
  • Log (14-14)
Utils/NetworkUtils.cpp (8)
  • ExtractIpFromHostPort (12-30)
  • ExtractIpFromHostPort (12-12)
  • IpPriority (74-88)
  • IpPriority (74-74)
  • SplitIPAddress (105-123)
  • SplitIPAddress (105-105)
  • GetBroadcastIP (125-170)
  • GetBroadcastIP (125-125)
State/MDNSHandler.cpp (2)
State/ApplicationState.h (1)
  • moonlight_xbox_dx (5-148)
State/MoonlightHost.h (1)
  • moonlight_xbox_dx (5-297)
App.xaml.cpp (1)
State/MDNSHandler.cpp (4)
  • close_mdns (474-483)
  • close_mdns (474-474)
  • reinit_mdns (485-492)
  • reinit_mdns (485-485)
🔇 Additional comments (21)
Package.appxmanifest (1)

49-50: Verify the necessity of the internetClientServer capability.

The PR introduces mDNS-based host discovery, which typically operates on the local network. The existing privateNetworkClientServer capability should be sufficient for local network mDNS operations. The internetClientServer capability grants broader internet access (both inbound and outbound), which may not be required unless the mDNS implementation needs to operate beyond the local network or supports internet-based discovery scenarios.

Can you confirm whether internet-level access is required for the mDNS implementation, or would privateNetworkClientServer alone suffice for local network discovery?

moonlight-xbox-dx.vcxproj.filters (1)

479-481: LGTM!

The addition of Utils\NetworkUtils.h to the Header Files filter is consistent with the new network utilities module introduced in this PR.

moonlight-xbox-dx.vcxproj (2)

385-385: LGTM!

The addition of Utils\NetworkUtils.h to the build is correct and follows the existing project structure.


560-560: LGTM!

The addition of Utils\NetworkUtils.cpp to the build is correct and completes the integration of the NetworkUtils module.

App.xaml.cpp (1)

9-9: LGTM!

The inclusion of State/MDNSHandler.h is necessary to support mDNS lifecycle management during app suspend/resume events.

State/MoonlightHost.h (1)

12-12: LGTM!

The addition of the MdnsInstanceName property follows the established pattern used by other properties in the class. The implementation correctly includes the private field, getter, setter, and property change notification for data binding support.

Also applies to: 55-62

State/MDNSHandler.h (1)

4-7: LGTM! Clean lifecycle API for mDNS management.

The new close_mdns() and reinit_mdns() functions provide proper lifecycle hooks for suspend/resume scenarios.

Pages/HostSelectorPage.xaml.cpp (3)

12-12: LGTM!

Include path for NetworkUtils is correctly added.


63-63: LGTM!

Passing nullptr for mdnsInstanceName is appropriate for manually-added hosts.


405-415: LGTM!

Good defensive fallback pattern for IP parsing. The catch-all gracefully handles malformed input.

State/ApplicationState.h (1)

20-21: LGTM!

Clean API additions for mDNS-based host management:

  • AddHost now accepts an optional mdnsInstanceName for deduplication.
  • UpdateHostAddressForInstance enables silent IP upgrades based on priority.
State/ApplicationState.cpp (2)

34-35: LGTM!

Correctly loading mdns_instance_name from persisted state and assigning to the new MdnsInstanceName property.


118-165: LGTM!

The UpdateHostAddressForInstance implementation correctly:

  • Validates input parameters
  • Matches hosts by MdnsInstanceName
  • Prevents IP regression using priority scoring
  • Persists changes via UpdateFile()
State/MDNSHandler.cpp (5)

16-18: LGTM!

The state maps (instanceToHost, instanceToPort, hostToIps) provide a clean structure for accumulating mDNS resolution data across multiple callbacks.


84-150: LGTM!

The dns_read_name function correctly handles DNS name compression with proper bounds checking and null-termination. The jump offset tracking for compression pointers is correctly implemented.


314-365: LGTM!

The resolution chain logic correctly:

  • Validates complete instance → hostname → IP → port mappings before proceeding
  • Selects the best IP using priority scoring
  • Properly formats IPv6 addresses with brackets
  • Attempts silent upgrade via UpdateHostAddressForInstance before adding new hosts
  • Safely modifies maps while iterating using iterator-based erase

377-398: IPv6 mDNS sockets not opened; discovery limited to IPv4 interfaces.

The PR description mentions IPv6 support, but init_mdns() only opens IPv4 sockets (line 379 checks HostNameType::Ipv4). While AAAA records are parsed from responses, mDNS queries are only sent over IPv4 interfaces. This means hosts on IPv6-only networks may not be discovered.

Consider adding IPv6 socket support using mdns_socket_open_ipv6() if full IPv6 mDNS discovery is intended.


474-492: LGTM!

Lifecycle functions properly manage socket cleanup and state reset. reinit_mdns() correctly clears discovery state maps before re-initialization.

Utils/NetworkUtils.cpp (3)

12-30: LGTM!

Correctly handles both IPv4 (ip:port) and IPv6 ([ip]:port) formats with safe fallbacks for malformed input.


32-71: LGTM!

IP classification functions are sufficient for the use case of prioritizing connection addresses. Note: IsPrivateIpv4 doesn't include link-local (169.254.x.x) or loopback (127.x.x.x), which is appropriate since those shouldn't be used for host connections anyway.


105-123: SplitIPAddress with : delimiter doesn't handle IPv6 addresses correctly.

Two callers (HostSelectorPage.xaml.cpp:406 and ApplicationState.cpp:350) use : as a delimiter to split hostname and port. However, IPv6 addresses contain colons (e.g., ::1:8080 or 2001:db8::1:443), so this approach fails for IPv6. The function itself is correctly parameterized, but if these callers might receive IPv6 addresses, they need special handling (typically with brackets: [::1]:8080). Clarify whether these callers are IPv4-only, or add IPv6 support.

Comment thread App.xaml.cpp Outdated
Comment on lines +105 to +108

try {
close_mdns();
} catch(...) {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add logging for mDNS teardown failures.

Silently swallowing all exceptions during mDNS teardown masks potential errors that could help diagnose issues. While it's appropriate to prevent suspend failures due to mDNS cleanup problems, logging the exception would provide valuable diagnostic information.

📝 Suggested improvement
 
 	try {
 		close_mdns();
-	} catch(...) {}
+	} catch(...) {
+		Utils::Log("[App] Failed to close mDNS during suspend\n");
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
close_mdns();
} catch(...) {}
try {
close_mdns();
} catch(...) {
Utils::Log("[App] Failed to close mDNS during suspend\n");
}
🤖 Prompt for AI Agents
In @App.xaml.cpp around lines 105 - 108, Currently exceptions from close_mdns()
in App.xaml.cpp are swallowed, hiding teardown errors; update the try/catch to
catch std::exception and any other exceptions and log them via the existing
logging facility (e.g., processLogger or app logger) including the
exception.what() for std::exception and a generic message for unknown
exceptions, so change the empty catch(...) to one that logs the error and
retains the swallow behavior to avoid suspend failures while surfacing
diagnostics for close_mdns().

Comment thread App.xaml.cpp Outdated
Comment on lines +121 to +124

try {
reinit_mdns();
} catch(...) {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add logging for mDNS reinitialization failures.

Silently swallowing all exceptions during mDNS reinitialization masks potential errors. If reinit_mdns() fails, the app will continue but host discovery will be broken with no indication to the user or in logs. Adding error logging would help diagnose post-resume discovery issues.

📝 Suggested improvement
 
 	try {
 		reinit_mdns();
-	} catch(...) {}
+	} catch(...) {
+		Utils::Log("[App] Failed to reinitialize mDNS during resume\n");
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
reinit_mdns();
} catch(...) {}
try {
reinit_mdns();
} catch(...) {
Utils::Log("[App] Failed to reinitialize mDNS during resume\n");
}
🤖 Prompt for AI Agents
In @App.xaml.cpp around lines 121 - 124, The try/catch around reinit_mdns()
currently swallows all exceptions; update it to catch std::exception and a
fallback catch(...) and log the failure so mDNS reinit errors are visible—call
the project's logging facility (or OutputDebug/Trace) from the
catch(std::exception& e) with a message like "reinit_mdns failed: " + e.what(),
and in catch(...) log an "unknown exception in reinit_mdns" message; keep
calling reinit_mdns() in the try block and ensure the logs include enough
context to diagnose resume/discovery failures.

Comment on lines +102 to +115
Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync(
Windows::UI::Core::CoreDispatcherPriority::High,
ref new Windows::UI::Core::DispatchedHandler([this, host]() {

Utils::Logf("[AddHost] NEW HOST: InstanceId='%s' MdnsInstance='%s' LastHostname='%s'\n",
Utils::PlatformStringToStdString(host->InstanceId).c_str(),
host->MdnsInstanceName ? Utils::PlatformStringToStdString(host->MdnsInstanceName).c_str() : "(null)",
Utils::PlatformStringToStdString(host->LastHostname).c_str());

SavedHosts->Append(host);
}));

UpdateFile();
return true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Race condition: UpdateFile() may execute before host is appended.

UpdateFile() is called immediately after RunAsync dispatches the append operation. Since RunAsync is asynchronous, SavedHosts->Append(host) may not complete before UpdateFile() iterates over SavedHosts, causing the new host to be missed in the persisted state.

Suggested fix: Move UpdateFile inside the dispatcher callback
 	Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync(
 	    Windows::UI::Core::CoreDispatcherPriority::High,
 	    ref new Windows::UI::Core::DispatchedHandler([this, host]() {
 
 		    Utils::Logf("[AddHost] NEW HOST: InstanceId='%s' MdnsInstance='%s' LastHostname='%s'\n",
 		                Utils::PlatformStringToStdString(host->InstanceId).c_str(),
 		                host->MdnsInstanceName ? Utils::PlatformStringToStdString(host->MdnsInstanceName).c_str() : "(null)",
 		                Utils::PlatformStringToStdString(host->LastHostname).c_str());
 
 		    SavedHosts->Append(host);
+		    UpdateFile();
 	    }));
 
-	UpdateFile();
 	return true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync(
Windows::UI::Core::CoreDispatcherPriority::High,
ref new Windows::UI::Core::DispatchedHandler([this, host]() {
Utils::Logf("[AddHost] NEW HOST: InstanceId='%s' MdnsInstance='%s' LastHostname='%s'\n",
Utils::PlatformStringToStdString(host->InstanceId).c_str(),
host->MdnsInstanceName ? Utils::PlatformStringToStdString(host->MdnsInstanceName).c_str() : "(null)",
Utils::PlatformStringToStdString(host->LastHostname).c_str());
SavedHosts->Append(host);
}));
UpdateFile();
return true;
Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher->RunAsync(
Windows::UI::Core::CoreDispatcherPriority::High,
ref new Windows::UI::Core::DispatchedHandler([this, host]() {
Utils::Logf("[AddHost] NEW HOST: InstanceId='%s' MdnsInstance='%s' LastHostname='%s'\n",
Utils::PlatformStringToStdString(host->InstanceId).c_str(),
host->MdnsInstanceName ? Utils::PlatformStringToStdString(host->MdnsInstanceName).c_str() : "(null)",
Utils::PlatformStringToStdString(host->LastHostname).c_str());
SavedHosts->Append(host);
UpdateFile();
}));
return true;
🤖 Prompt for AI Agents
In @State/ApplicationState.cpp around lines 102 - 115, The race occurs because
UpdateFile() is called immediately after dispatching the UI thread lambda, so
SavedHosts->Append(host) (inside the RunAsync lambda) may not have run yet; fix
by invoking UpdateFile() from inside the dispatched handler immediately after
SavedHosts->Append(host) (i.e., move the UpdateFile() call into the lambda that
runs in Windows::UI::Core::DispatchedHandler) so the file update happens only
after the host is appended (alternatively capture the IAsyncAction returned by
RunAsync and wait for completion before calling UpdateFile(), but the simplest
fix is to call UpdateFile() right after SavedHosts->Append(host) within the
dispatched callback).

nlohmann::json hostJson;
hostJson["hostname"] = Utils::PlatformStringToStdString(host->LastHostname);
hostJson["instance_id"] = Utils::PlatformStringToStdString(host->InstanceId);
hostJson["mdns_instance_name"] = Utils::PlatformStringToStdString(host->MdnsInstanceName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -nA10 "PlatformStringToStdString" --type=cpp | head -100

Repository: TheElixZammuto/moonlight-xbox

Length of output: 5888


🏁 Script executed:

sed -n '180,195p' State/ApplicationState.cpp | cat -n

Repository: TheElixZammuto/moonlight-xbox

Length of output: 1206


🏁 Script executed:

# Check host definition and MdnsInstanceName initialization
rg -nB3 "MdnsInstanceName" State/ApplicationState.cpp | head -50

Repository: TheElixZammuto/moonlight-xbox

Length of output: 2181


Add null-check before converting host->MdnsInstanceName to string.

Line 186 calls Utils::PlatformStringToStdString(host->MdnsInstanceName) without checking for nullptr. The PlatformStringToStdString implementation (Utils.cpp:117-119) does not handle nullptr input and will crash. Since MdnsInstanceName can be nullptr for manually-added hosts (see lines 81-82, 108, and 131 which already guard against this), line 186 must use the same pattern: host->MdnsInstanceName != nullptr ? Utils::PlatformStringToStdString(host->MdnsInstanceName) : "".

🤖 Prompt for AI Agents
In @State/ApplicationState.cpp at line 186, hostJson["mdns_instance_name"] is
set using Utils::PlatformStringToStdString(host->MdnsInstanceName) without a
nullptr check; change this to guard against host->MdnsInstanceName being nullptr
(the same pattern used elsewhere) by using a conditional expression so that if
host->MdnsInstanceName is nullptr you assign an empty string, otherwise call
Utils::PlatformStringToStdString(host->MdnsInstanceName), keeping the assignment
to hostJson["mdns_instance_name"] unchanged.

Comment on lines 348 to 363
if (host->LastHostname)
{
auto hostnameSplit = Split_IP_Address(Utils::PlatformStringToStdString(host->LastHostname), ':');
auto hostnameSplit = NetworkUtils::SplitIPAddress(Utils::PlatformStringToStdString(host->LastHostname), ':');
std::string hostIp = hostnameSplit.first;
addresses.push_back(hostIp);
}

if (inet_addr(Utils::PlatformStringToStdString(host->ServerAddress).c_str()) != -1)
{
std::string broadcastIP = Get_Broadcast_IP(Utils::PlatformStringToStdString(host->ServerAddress));
if (broadcastIP != "")
std::string broadcastIP = NetworkUtils::GetBroadcastIP(Utils::PlatformStringToStdString(host->ServerAddress));
if (broadcastIP != "")
{
addresses.push_back(broadcastIP);
} else {
Utils::Log("Could not determine subnet mask from IP address.\n");
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing exception handling for SplitIPAddress and GetBroadcastIP.

Both NetworkUtils::SplitIPAddress (line 350) and NetworkUtils::GetBroadcastIP (line 357) can throw exceptions (std::invalid_argument, std::out_of_range). If LastHostname or ServerAddress contains malformed data, this will propagate up and potentially crash the WoL flow.

Suggested fix: Wrap in try-catch
 	if (host->LastHostname)
 	{
+		try {
 		auto hostnameSplit = NetworkUtils::SplitIPAddress(Utils::PlatformStringToStdString(host->LastHostname), ':');
 		std::string hostIp = hostnameSplit.first;
 		addresses.push_back(hostIp);
+		} catch (...) {
+			Utils::Log("Failed to parse LastHostname for WoL\n");
+		}
 	}
 
 	if (inet_addr(Utils::PlatformStringToStdString(host->ServerAddress).c_str()) != -1)
 	{
-		std::string broadcastIP = NetworkUtils::GetBroadcastIP(Utils::PlatformStringToStdString(host->ServerAddress));
-		if (broadcastIP != "") 
-		{
-			addresses.push_back(broadcastIP);
-		} else {
-			Utils::Log("Could not determine subnet mask from IP address.\n");
+		try {
+			std::string broadcastIP = NetworkUtils::GetBroadcastIP(Utils::PlatformStringToStdString(host->ServerAddress));
+			if (broadcastIP != "") {
+				addresses.push_back(broadcastIP);
+			} else {
+				Utils::Log("Could not determine subnet mask from IP address.\n");
+			}
+		} catch (...) {
+			Utils::Log("Failed to calculate broadcast IP for WoL\n");
 		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (host->LastHostname)
{
auto hostnameSplit = Split_IP_Address(Utils::PlatformStringToStdString(host->LastHostname), ':');
auto hostnameSplit = NetworkUtils::SplitIPAddress(Utils::PlatformStringToStdString(host->LastHostname), ':');
std::string hostIp = hostnameSplit.first;
addresses.push_back(hostIp);
}
if (inet_addr(Utils::PlatformStringToStdString(host->ServerAddress).c_str()) != -1)
{
std::string broadcastIP = Get_Broadcast_IP(Utils::PlatformStringToStdString(host->ServerAddress));
if (broadcastIP != "")
std::string broadcastIP = NetworkUtils::GetBroadcastIP(Utils::PlatformStringToStdString(host->ServerAddress));
if (broadcastIP != "")
{
addresses.push_back(broadcastIP);
} else {
Utils::Log("Could not determine subnet mask from IP address.\n");
}
if (host->LastHostname)
{
try {
auto hostnameSplit = NetworkUtils::SplitIPAddress(Utils::PlatformStringToStdString(host->LastHostname), ':');
std::string hostIp = hostnameSplit.first;
addresses.push_back(hostIp);
} catch (...) {
Utils::Log("Failed to parse LastHostname for WoL\n");
}
}
if (inet_addr(Utils::PlatformStringToStdString(host->ServerAddress).c_str()) != -1)
{
try {
std::string broadcastIP = NetworkUtils::GetBroadcastIP(Utils::PlatformStringToStdString(host->ServerAddress));
if (broadcastIP != "") {
addresses.push_back(broadcastIP);
} else {
Utils::Log("Could not determine subnet mask from IP address.\n");
}
} catch (...) {
Utils::Log("Failed to calculate broadcast IP for WoL\n");
}
}
🤖 Prompt for AI Agents
In @State/ApplicationState.cpp around lines 348 - 363, Wrap the calls to
NetworkUtils::SplitIPAddress and NetworkUtils::GetBroadcastIP in try-catch
blocks to prevent exceptions from propagating when LastHostname or ServerAddress
are malformed; catch std::invalid_argument and std::out_of_range (or
std::exception as a fallback), log an informative message via Utils::Log
(including the offending LastHostname/ServerAddress) and skip pushing results
into the addresses vector so the WoL flow continues safely. Ensure the try-catch
surrounds the SplitIPAddress call where host->LastHostname is used and the
GetBroadcastIP call where host->ServerAddress is used, and do not rethrow the
exceptions.

Comment thread State/MDNSHandler.cpp
Comment on lines +27 to +34
static mdns_string_t ipv4_address_to_string(char *buffer, size_t capacity, const struct sockaddr_in *addr, size_t addrlen) {
char host[NI_MAXHOST] = { 0 };
char service[NI_MAXSERV] = { 0 };
int ret = getnameinfo((const struct sockaddr*)addr, (socklen_t)addrlen, host, NI_MAXHOST,
service, NI_MAXSERV, NI_NUMERICSERV | NI_NUMERICHOST);
int ret = getnameinfo(
(const struct sockaddr *)addr, (socklen_t)addrlen,
host, NI_MAXHOST,
service, NI_MAXSERV,
NI_NUMERICSERV | NI_NUMERICHOST);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Variable shadowing: local service shadows global service.

The local variable service (line 29) shadows the file-scope service constant (line 14). While this works correctly in the current code, it could cause confusion during maintenance.

Suggested fix: Rename local variable
 static mdns_string_t ipv4_address_to_string(char *buffer, size_t capacity, const struct sockaddr_in *addr, size_t addrlen) {
 	char host[NI_MAXHOST] = { 0 };
-	char service[NI_MAXSERV] = { 0 };
+	char svc[NI_MAXSERV] = { 0 };
 	int ret = getnameinfo(
 	    (const struct sockaddr *)addr, (socklen_t)addrlen,
 	    host, NI_MAXHOST,
-	    service, NI_MAXSERV,
+	    svc, NI_MAXSERV,
 	    NI_NUMERICSERV | NI_NUMERICHOST);
 
 	int len = 0;
 	if (ret == 0) {
 		if (addr->sin_port != 0) {
-			len = snprintf(buffer, capacity, "%s:%s", host, service);
+			len = snprintf(buffer, capacity, "%s:%s", host, svc);
 		} else {

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @State/MDNSHandler.cpp around lines 27 - 34, In ipv4_address_to_string, the
local variable named "service" shadows the file-scope "service" constant; rename
the local to something distinct (e.g., service_buf or service_local) and update
all uses within ipv4_address_to_string (including the getnameinfo call and any
subsequent references) to avoid the shadowing while preserving the buffer size
NI_MAXSERV.

Comment thread Utils/NetworkUtils.cpp
Comment on lines +154 to +157
} else {
WSACleanup();
return "";
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Misplaced WSACleanup() call corrupts Winsock reference count.

GetBroadcastIP calls WSACleanup() on the error path (line 155), but this function doesn't call WSAStartup(). Since Winsock uses reference counting, this will decrement the count incorrectly, potentially breaking subsequent socket operations in other parts of the application.

Suggested fix: Remove the errant WSACleanup call
 	} else if ((ipAddress_int & 0xC0000000) == 0xC0000000) { // Class C
 		subnetMask = 4294967040;                             // 255.255.255.0
 	} else {
-		WSACleanup();
 		return "";
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else {
WSACleanup();
return "";
}
} else if ((ipAddress_int & 0xC0000000) == 0xC0000000) { // Class C
subnetMask = 4294967040; // 255.255.255.0
} else {
return "";
}
🤖 Prompt for AI Agents
In @Utils/NetworkUtils.cpp around lines 154 - 157, The GetBroadcastIP function
incorrectly calls WSACleanup() on its error path, which will decrement Winsock's
reference count even though GetBroadcastIP does not call WSAStartup(); remove
the errant WSACleanup() invocation from GetBroadcastIP (and any similar
early-return error branches) so Winsock cleanup is only performed by the code
that called WSAStartup() or a dedicated shutdown routine; if necessary, ensure
any WSAStartup() caller is responsible for a matching WSACleanup() instead of
doing cleanup inside GetBroadcastIP.

@mpotrykus
mpotrykus marked this pull request as draft January 14, 2026 16:53
@mpotrykus

Copy link
Copy Markdown
Contributor Author

Using more this week, still seeing intermittent issues with host connections dropping.

Going to continue to work on this.

@mpotrykus mpotrykus changed the title Network Improvements [Feature] Network Improvements Jan 14, 2026
@mpotrykus mpotrykus changed the title [Feature] Network Improvements [Refactor] Network Improvements Jan 14, 2026
@mpotrykus

Copy link
Copy Markdown
Contributor Author

Opening back up.

This was not the culprit for the network dropping issue, but it does add features and improvements.

@mpotrykus
mpotrykus marked this pull request as ready for review February 24, 2026 12:35
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.

1 participant