CNA-BUG-170: A joined SystemLink session answers LAN discovery for itself, so Find lists the session once per member

CNA snapshot 3b43bd86  ·  Known Issues › Current bugs  ·  source links pinned to 3b43bd86

✓

Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 3b43bd86; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

Every SystemLink session, including a joined one, registers with discovery and ReplyToQuery ignores IsHost, so Find lists a session once per member; joining through a client's entry is refused by that client instead of reaching the host.

Identifier
CNA-BUG-170
Category
Bug
Subsystem
Networking & gamer services
Status
Narrowed (partially fixed; describes only what survives)
Verified against
CNA 3b43bd86 (3b43bd8687ed46523d873f9bec4b32bd8481dcb5)
Severity
Medium (a triage suggestion, not a project priority)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
Microsoft::Xna::Framework::Net::NetworkSession::Find and Join for NetworkSessionType::SystemLink (ENetDiscoveryService::RegisterHost and ReplyToQuery; ENetBackend::StartHosting and HandleClientHello)

Expected behaviour

XNA's Find returns the available sessions, one entry per hosted session, and joining an entry connects to that session's host. CNA's discovery code itself describes the reply as coming from a hosting session.

Actual behaviour at TARGET

The NetworkSession constructor calls ENetBackend::StartHosting for every SystemLink session, including the one EndJoin constructs with isHost false, and StartHosting calls ENetDiscoveryService::RegisterHost(session, port) (ENetBackend.cpp). ReplyToQuery in ENetDiscoveryService.cpp answers any query with a matching type filter with the registered session's port, gamer counts and host gamertag, without checking that the session is the host. A client therefore advertises the session it joined: with host A and client B, a Find on machine C returns two entries with the same host gamertag (results are de-duplicated by connect port, and the ports differ). If C joins B's entry, B's HandleClientHello now sees that its HostPeer names another machine, logs a warning and disconnects C, so the join fails although the session C saw is open. That every peer keeps an accepting ENet host is intended (host migration reuses it); the discovery registration is what exposes it.

Source locations

Evidence

Checked by reading at 3b43bd86; not executed. The two-entry Find result follows from the code paths named above and was not observed on a network. No test has a joined client and a third searcher: the discovery tests register one host, and the two-process test hands the host's port to one client through a pipe. Within one process only one session can exist, so the single registered-session pointer is not itself a problem.

Narrowed at c6dec91f: a83673fc5 (2026-10-14) added the host check this entry also described as missing: a client-role session refuses a ClientHello while state.HostPeer names another host, so a third machine can no longer split the session by joining a client (ENetBackendTest.ClientRejectsClientHelloFromRogueThirdPartyPeerOnItsOwnListeningSocket). The client's discovery registration and ReplyToQuery are unchanged.

Focused reproduction

// Illustrative, three machines on one LAN; not run.
// A: hosts with NetworkSession::Create(NetworkSessionType::SystemLink, 1, 8).
// B: finds A and joins it.
// C:
AvailableNetworkSessionCollection found =
    NetworkSession::Find(NetworkSessionType::SystemLink, 1, NetworkSessionProperties{});
// found holds two entries with A's gamertag as host: A's port and B's port.

Current tests

ENetDiscoveryServiceTests.cpp (for example FindSessionsDiscoversRegisteredHost and ReplyToQueryOnlyAnswersWhenSessionTypeFilterMatchesTheHost) registers one host; ENetBackendTests.cpp covers the refused ClientHello; no test registers a client session and checks that it stays silent.

Regression test

A discovery test that registers a client session and expects no announcement for it.

Blast radius

LAN games with a host and at least one joined client while another machine searches: duplicate listings, and a player who picks a non-host entry is refused. Two-machine games and Local sessions are unaffected. Host migration's rediscovery also searches by host gamertag and may see these client announcements; that interaction was not analysed.

Workaround

No workaround is known.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index