Skip to content

Add lab.ResolveOverTCP: a firewall-robust TCP-probe discovery fallback#279

Open
cboulay wants to merge 2 commits into
devfrom
cboulay/resolve_over_tcp
Open

Add lab.ResolveOverTCP: a firewall-robust TCP-probe discovery fallback#279
cboulay wants to merge 2 commits into
devfrom
cboulay/resolve_over_tcp

Conversation

@cboulay

@cboulay cboulay commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

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 ResolveOverTCP is enabled, the resolver builds a target list of loopback (127.0.0.1/::1) + every KnownPeer, each crossed with the BasePort..BasePort+PortRange range. Alongside the normal UDP waves it fires a TCP probe burst (resolve_attempt_tcp): for each target it connects, sends LSL:shortinfo, reads the reply, and stores the resulting stream_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 WARNING is logged when the option is enabled. Best suited to loopback / a small static KnownPeers set, not broad discovery.

Changes

  • api_configlab.ResolveOverTCP option + getter + enable-time warning.
  • resolve_attempt_tcp (new) — async, worker-pool TCP prober mirroring resolve_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 TCP LSL:shortinfo responder now keeps the client session alive through the reply write (it previously captured only the tcp_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, like runtime_config, since it sets the config singleton): blinds UDP discovery completely (ResolveScope=machine with empty MachineAddresses, empty KnownPeers) 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, and runtime_config are unaffected.

Notes

cboulay added 2 commits June 16, 2026 21:51
…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".
@cboulay

cboulay commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

This PR might enable resolution via websockets. See #88

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