Skip to content

olcRTC: connect end-to-end over a public meet carrier (signaling + peer-connection fixes) - #119

Merged
hawkff merged 8 commits into
mainfrom
fix/olcrtc-signaling-ipv4
Jul 4, 2026
Merged

hawkff merged 8 commits into
mainfrom
fix/olcrtc-signaling-ipv4

Conversation

@hawkff

@hawkff hawkff commented Jul 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

Makes the olcRTC sidecar connect end-to-end in VPN/tun mode against a public
anonymous meet carrier, verified on Android. The connect previously stalled at
signaling on one carrier and then failed at peer-connection setup on another; this
PR lands the signaling robustness work plus the peer-connection fix.

Changes

  • Signaling transport hardening (buildScript/lib/olcrtc-src/main.go): the
    protected signaling dials are pinned to HTTP/1.1 (the WebSocket upgrade cannot ride
    an h2 connection), forced to IPv4 on the physical path, MSS-clamped, and given
    explicit handshake/response timeouts. A debug-only stage trace (httptrace + a
    TCP_INFO poll, unwrapping the TLS conn to read the advertised MSS) makes any stall
    attributable to a specific stage instead of surfacing as silence. All of this is
    gated behind -debug.
  • Peer-connection fix (sidecar pin): point the sidecar at the maintained fork at a
    commit that sanitizes the ICE server list before building the peer connection. Some
    deployments advertise TURN/STUN over service discovery without a port; the resulting
    URL has an empty port and the WebRTC stack rejected the whole config before any
    candidate was added. Dropping the malformed entries lets the connection construct;
    routable host candidates plus the bridge channel establish the data channel without
    those relays. Pin updated in both buildScript/lib/olcrtc.sh and
    .github/workflows/ci.yml.

Verification

  • CI green (repo guards, native builds, lint, unit tests, APK).
  • Verified on Android in VPN mode against a public anonymous meet room with a matching
    server: the peer connection establishes, the data channel opens, the session pairs,
    and real traffic egresses through the tunnel (server-side traffic counters confirm
    bytes in/out for live destinations). No local data was wiped; the release-signed CI
    APK installs over the prior build.

The sidecar's protected resolver + http.DefaultTransport dialer (used for the Jitsi
XMPP websocket/BOSH signaling) dialed whatever the resolver returned. For a carrier
host that also publishes an AAAA (e.g. framatalk.org -> 2a01:4f8:231:1213::2) the
protected socket, marked for the physical interface, dialed the v6 address; on a
v4-only physical path that SYN is blackholed and the dial hangs silently until the
readiness deadline (observed on device: the sidecar logs 'joining MUC' then nothing,
then 'wait for peer: context deadline exceeded' after 60s). A carrier with no usable
AAAA (meet.ffmuc.net) joined fine, and a normal host joins framatalk fine via Happy
Eyeballs v4 fallback — which the custom protected dialer does not do.

Force the protected resolver lookups and the signaling dials to tcp4/udp4 so a
published AAAA can never cause a silent hang on a v4-only routed path. Media/ICE
(pion) already falls back to v4 candidates.

Verified root cause on-device (framatalk AAAA + no global IPv6 on the test WiFi) and
on the server (signaling joined over v4; only ICE tried v6 and fell back).
@coderabbitai

coderabbitai Bot commented Jul 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The protected resolver and HTTP transport now force IPv4 dialing where appropriate, apply TCP tuning on protected sockets, add HTTP timing settings, and enable debug-only request tracing with TCP MSS logging.

Changes

Protected dial and trace setup

Layer / File(s) Summary
Resolver IPv4 rewrite
buildScript/lib/olcrtc-src/main.go
Passes debug into installProtectedDefaults, adds forceIPv4Network, sets TCP socket tuning on protected sockets, and rewrites protected resolver dialing based on whether the DNS server is effectively IPv4.
HTTP transport tracing
buildScript/lib/olcrtc-src/main.go
Rebuilds the default HTTP transport with protected dialing, IPv4 forcing, explicit TLS and header timeouts, HTTP/2 disabled for signaling, debug-only httptrace logging, and TCP MSS logging from the TLS-backed connection.

Estimated code review effort: 4 (Complex) | ~45 minutes

Poem

A bunny tuned the sockets bright,
To keep the dials in IPv4 sight.
Debug traced hops from start to byte,
While MSS hopped along just right. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly reflects the main end-to-end connection fixes for olcRTC, including signaling and peer-connection work.
Description check ✅ Passed The description matches the changeset, covering signaling hardening, peer-connection fixes, and verification details.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
buildScript/lib/olcrtc-src/main.go (1)

104-109: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Clarify that IPv4 selection comes from forceTCP4, not the resolver

Resolver.Dial only controls how DNS is reached; it doesn’t make lookups A-only. Reword this to attribute the IPv6 avoidance to the transport-level forceTCP4 on the target dial.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@buildScript/lib/olcrtc-src/main.go` around lines 104 - 109, Clarify the
IPv4-only comment by attributing IPv6 avoidance to the transport-level forceTCP4
behavior rather than Resolver.Dial. Update the explanatory comment near the
IPv4-only signaling dial logic in main.go so it states that forceTCP4 on the
target dial keeps the connection on tcp4 and prevents hanging on unreachable
AAAA addresses, while Resolver.Dial only affects how DNS is reached.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@buildScript/lib/olcrtc-src/main.go`:
- Around line 104-109: Clarify the IPv4-only comment by attributing IPv6
avoidance to the transport-level forceTCP4 behavior rather than Resolver.Dial.
Update the explanatory comment near the IPv4-only signaling dial logic in
main.go so it states that forceTCP4 on the target dial keeps the connection on
tcp4 and prevents hanging on unreachable AAAA addresses, while Resolver.Dial
only affects how DNS is reached.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0d094a90-4495-43d3-ad08-db162a0f148b

📥 Commits

Reviewing files that changed from the base of the PR and between e2ffd10 and b54d344.

📒 Files selected for processing (1)
  • buildScript/lib/olcrtc-src/main.go

Comment thread buildScript/lib/olcrtc-src/main.go Outdated
Comment thread buildScript/lib/olcrtc-src/main.go
hawkff added 2 commits July 3, 2026 19:05
… working

- forceTCP4 -> forceIPv4Network: the helper also rewrites udp/udp6, so the
  old name understated its scope.
- Skip the udp4/tcp4 rewrite for resolver queries when -dns is an IPv6
  literal (zone-scoped literals handled via netip.ParseAddr); forcing a
  v4 transport toward a v6 server would fail every lookup.
- Clarify the doc comment: A-only lookups come from the transport dialing
  tcp4, not from the resolver rewrite.
…meouts

The IPv4 forcing did not resolve the observed device stall where the sidecar logs
'joining MUC' then reads nothing for the full readiness window. The signature
(host-specific, network-specific, IPv4-indifferent, zero output until deadline) is a
reduced-MTU path dropping an oversized inbound TLS certificate flight mid-handshake,
so the read blocks. Clamp TCP_MAXSEG to 1200 on the protected signaling sockets in the
existing socket Control hook so those inbound segments fit on a routed path.

Also restore explicit transport timeouts (add ResponseHeaderTimeout; TLSHandshakeTimeout
was already set) so a future stall surfaces as a typed error in seconds instead of
blocking silently, and add a debug-only httptrace RoundTripper that logs each request's
connection stages (dns / connect / tls / headers / first byte) to attribute a stall to
a specific stage. Trace is gated behind -debug (it logs request URLs).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@buildScript/lib/olcrtc-src/main.go`:
- Line 192: The shared default HTTP transport is forcing HTTP/2, which can
interfere with websocket Upgrade handshakes used through the j library’s
`http.DefaultClient`. Update the transport configuration in `main.go` to avoid
enabling HTTP/2 on the shared default transport, either by setting
`ForceAttemptHTTP2` off or by moving websocket traffic to a separate transport.
Use the existing transport setup near `ForceAttemptHTTP2` to locate the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 471ff651-88ea-4574-9482-4c15a96a0d71

📥 Commits

Reviewing files that changed from the base of the PR and between b54d344 and 1f1b502.

📒 Files selected for processing (1)
  • buildScript/lib/olcrtc-src/main.go

Comment thread buildScript/lib/olcrtc-src/main.go Outdated
hawkff added 5 commits July 3, 2026 19:53
…stall)

Device stage-tracing pinpointed the stall: framatalk.org negotiates HTTP/2 via ALPN on
the first signaling connection, and the XMPP client's WebSocket upgrade cannot ride an
h2 connection, so the upgrade request hangs with no response until the header timeout;
only a later HTTP/1.1 attempt returns the 101 and the XMPP stream opens (trace showed
tls-done proto=h2 -> wrote-headers -> [15s silence] -> retry with proto="" -> first-byte
-> full XMPP flow). A carrier that serves the signaling over h1 (meet.ffmuc.net) never
hit this.

Disable HTTP/2 on the signaling transport (ForceAttemptHTTP2=false + non-nil empty
TLSNextProto) so the WebSocket upgrade always uses h1 and connects on the first attempt,
removing the wasted round that was eating into the readiness window. The MSS clamp,
restored timeouts, and debug stage-tracing from the previous commit stay.
A device run with the MSS=1200 clamp still stalled ~15s on the first large inbound
payload (GET /config.js body) and got no reply to the Jicofo conference IQ (and no
stream-management ack either) — the classic signature of a reduced-MTU inbound
blackhole with TCP head-of-line blocking: small inbound stanzas (bind/session/enabled)
arrive, large ones (config body, Jicofo's conference reply) are dropped and stall the
whole stream. Small inbound payloads working while large ones stall, in the same
direction, size-correlated, points at the advertised MSS not being small enough for the
path (or not applied).

Lower TCP_MAXSEG to 1000, surface the setsockopt error under -debug, and log the live
connection's TCP_INFO (advmss/rcv_mss/snd_mss) in the debug trace so the next run proves
whether the clamp reached the SYN (advmss ~= 1000 vs the ~1460 default).
The advmss/rcv_mss diagnostic asserted syscall.Conn directly on the
connection handed to the httptrace GotConn hook. That connection is a
*tls.Conn, which exposes NetConn() rather than SyscallConn(), so the
assertion always failed and the line was silently omitted. Unwrap the
TLS layer via NetConn() first so the trace actually reports the values;
log the concrete type when the unwrap still does not yield a syscall.Conn.
Two diagnostic-driven changes on the protected signaling sockets, both
debug-gated via the existing trace path:

- Set SO_RCVBUF pre-connect (32 KiB). The TCP window scale is fixed from
  the receive buffer at SYN time, so a small buffer shrinks the advertised
  window and forces the peer to deliver its response in several small
  flights rather than one large burst. This targets a middlebox that
  mishandles window scaling or polices large bursts on the routed path,
  whose signature (small inbound fine, large inbound stalls even with the
  segment-size clamp proven on the wire) matches what the trace shows.

- Poll TCP_INFO on the connection every 500ms for ~16s after it is
  established, logging bytes_received / segs_in / rcv_ooopack / rcv_wnd.
  During a large-inbound stall this distinguishes 'nothing arrives'
  (return-path drop, no client fix) from 'segments arrive out of order
  with a hole at the front' (size-selective drop). The protected socket
  bypasses the tun, so this is the only on-device observation available
  to an unprivileged process.
Point the olcRTC sidecar build at the hawkff fork at the commit that adds
ICE-server URL sanitization. Deployments whose XEP-0215 disco advertises
TURN/STUN without a port previously made pion reject the peer connection
config ("invalid port") before the data channel could start; the fork drops
those malformed entries so the media path constructs. Repo + commit are set
in both the build script default and the CI workflow env.
@hawkff hawkff changed the title olcRTC: force IPv4 for protected signaling dials olcRTC: connect end-to-end over a public meet carrier (signaling + peer-connection fixes) Jul 4, 2026
@hawkff
hawkff merged commit e414d68 into main Jul 4, 2026
8 checks passed
@hawkff
hawkff deleted the fix/olcrtc-signaling-ipv4 branch July 4, 2026 09:44
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