Add lab.ResolveOverTCP: a firewall-robust TCP-probe discovery fallback#279
Open
cboulay wants to merge 2 commits into
Open
Add lab.ResolveOverTCP: a firewall-robust TCP-probe discovery fallback#279cboulay wants to merge 2 commits into
cboulay wants to merge 2 commits into
Conversation
…fo reply The TCP shortinfo responder wrote the reply with a completion handler that captured only the tcp_server, not the client_session that owns the socket, so the session (and its socket) could be destroyed mid-write. Capture shared_this so the session lives until the shortinfo has been sent and is then destroyed, closing the socket so the client sees EOF and knows the reply is complete (matching the LSL:fullinfo path). This path had no consumer until now; the ResolveOverTCP fallback added next is the first to use it.
Adds an opt-in (default off) discovery channel that resolves streams by connecting to their TCP data ports directly, instead of relying on UDP multicast/broadcast/unicast. When enabled, a resolve also probes every port in the BasePort..BasePort+PortRange range on loopback and on each KnownPeer, sending an LSL:shortinfo query and parsing the reply. Because TCP is a symmetric, connection-oriented protocol it is robust against the stateful-firewall issues that can silently drop UDP discovery replies, at the cost of being much slower (one connection attempt per port per host, and filtered remote ports cost a full connect timeout each). A warning is logged when the option is enabled. - api_config: lab.ResolveOverTCP option + getter - resolve_attempt_tcp: async, worker-pool TCP prober mirroring resolve_attempt_udp - resolver_impl: builds loopback + KnownPeers TCP targets and fires a throttled TCP burst per wave, guarded against overlapping bursts - test: resolve_over_tcp finds a local stream with UDP discovery fully blinded Note: liblsl config booleans are 1/0, so the setting is "ResolveOverTCP = 1".
Collaborator
Author
|
This PR might enable resolution via websockets. See #88 |
This was referenced Jun 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an opt-in, firewall-robust stream-discovery fallback:
lab.ResolveOverTCP(default off). When enabled, a resolve additionally finds streams by connecting to their TCP data ports directly, instead of relying solely on UDP multicast/broadcast/unicast.This complements (and is independent of) #278: where #278 makes UDP discovery itself firewall-friendly, this PR provides a path that avoids UDP discovery entirely for same-machine and known-host setups.
Why
UDP discovery replies can be silently dropped by stateful firewalls (and UDP multicast can be blocked on many networks). TCP is a symmetric, connection-oriented protocol that a stateful firewall tracks natively, so probing the data ports directly is robust where UDP discovery isn't. The cost is speed — see below.
How it works
When
ResolveOverTCPis enabled, the resolver builds a target list of loopback (127.0.0.1/::1) + everyKnownPeer, each crossed with theBasePort..BasePort+PortRangerange. Alongside the normal UDP waves it fires a TCP probe burst (resolve_attempt_tcp): for each target it connects, sendsLSL:shortinfo, reads the reply, and stores the resultingstream_info(host taken from the dialed endpoint, ports from the reply XML). Probes run through a small worker pool (cap 16), bounded by a deadline and cancelled the moment enough streams are found.Cost (documented loudly)
This is slow: one connection attempt per port per host, and closed/filtered remote ports each cost a full connect timeout. A
WARNINGis logged when the option is enabled. Best suited to loopback / a small staticKnownPeersset, not broad discovery.Changes
api_config—lab.ResolveOverTCPoption + getter + enable-time warning.resolve_attempt_tcp(new) — async, worker-pool TCP prober mirroringresolve_attempt_udp's structure and cancellation.resolver_impl— builds the TCP target list; fires a throttled TCP burst per wave, guarded against overlapping bursts via an in-flight check.tcp_server— prerequisite fix: the TCPLSL:shortinforesponder now keeps the client session alive through the reply write (it previously captured only thetcp_server, risking a destroy-mid-write). This path had no consumer before; this PR is the first.Test
testing/int/resolve_over_tcp.cpp(own executable, likeruntime_config, since it sets the config singleton): blinds UDP discovery completely (ResolveScope=machinewith emptyMachineAddresses, emptyKnownPeers) and asserts a local stream is still resolved — so the hit can only have come from the TCP probe.Testing
Full suite green locally (macOS universal): the new test passes, and with the feature off (default)
discovery(live UDP resolution, 730 assertions),network,tcpserver, andruntime_configare unaffected.Notes
ResolveOverTCP = 1is set.1/0(from_string<bool>isstr == "1"), so the setting isResolveOverTCP = 1. Thelslapicfgdocs (separate repo) should document the option and the slowness warning.