CNA-BUG-170: A joined SystemLink session answers LAN discovery for itself and handles ClientHello as if it were the host

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

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 009d40f5; 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 and joining through a client's entry attaches to that client instead of the host.

Identifier
CNA-BUG-170
Category
Bug
Subsystem
Networking & gamer services
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
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 backend receives C's ClientHello and processes it in HandleClientHello, which has no host check (its comment reads "We are the host handling an incoming ClientHello"): B assigns wire ids from its own table, sends C a ServerWelcome and adds C's gamers to B's session only. A never learns of C. That every peer keeps an accepting ENet host is intended (host migration reuses it); the discovery registration and the missing host check are what expose it.

Source locations

Evidence

Checked by reading at 009d40f5; not executed. The two-entry Find result and the split roster follow from the code paths named above and were 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.

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 and TwoProcessLoopbackTest.cpp connect clients by port.

Regression test

A discovery test that registers a client session and expects no announcement, and a backend test in which a client's ENet host receives a ClientHello and disconnects the peer instead of welcoming it. Both run in one process.

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 ends up in a split session whose roster and packets reach only that client. 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