Skip to content

feat(rtcconfig): add advertise_private_ips for hybrid topologies#84

Closed
lccool6683 wants to merge 1 commit into
livekit:masterfrom
lccool6683:feat/advertise-private-ips
Closed

feat(rtcconfig): add advertise_private_ips for hybrid topologies#84
lccool6683 wants to merge 1 commit into
livekit:masterfrom
lccool6683:feat/advertise-private-ips

Conversation

@lccool6683

Copy link
Copy Markdown

What

Adds opt-in RTCConfig.AdvertisePrivateIPs (yaml: advertise_private_ips, default false). When enabled together with use_external_ip, NewWebRTCConfig publishes both the private host candidate and the STUN-derived public IP as a server-reflexive candidate, instead of replacing the host candidate with the public IP.

Implements the design discussed in livekit/livekit#4487.

Why

Unblocks hybrid Kubernetes topologies where some peers reach LiveKit via cluster-internal routing on the private IP while others reach it via the public IP. Without this, the existing use_external_ip path replaces the private host candidate with the public IP, leaving in-cluster peers unable to take a direct host path.

How

  • chooseNAT1To1Mode(advertisePrivateIPs, externalIPOnly, externalMappingsCount) picks (Replace, Host) by default and (Append, Srflx) only when advertise_private_ips=true, external_ip_only=false, and STUN actually returned mappings.
  • SetNAT1To1AddressRewriteRulesAppendSrflx wires the new mode into pion's webrtc.SetICEAddressRewriteRules in append mode.
  • buildNAT1To1Rules is the shared rule builder. The catch-all rule (one entry per external IP, no Local) is emitted regardless of mode — pion's gatherCandidatesSrflxMapped opens its UDP socket on 0.0.0.0, and rule lookup keyed on the wildcard local IP must hit the catch-all to publish the right external IP. Without it, srflx candidates would fall back to advertising 0.0.0.0 as base address.
  • Self-mappings (External == Local) are skipped in append mode because publishing <private> → <private> as srflx is meaningless and only inflates ICE check overhead.

Default behavior

Default false. When advertise_private_ips=false, the rewrite path is unchanged from master (legacy Replace+Host semantics preserved through SetNAT1To1AddressRewriteRulesdefaultNAT1To1Mode).

Tests

go test -race ./pkg/rtcconfig/ green. Added coverage:

  • TestBuildNAT1To1Rules — replace+host shape, append+srflx shape, append+srflx with self-mapping skip, append+srflx with unmapped catch-all.
  • TestChooseNAT1To1Mode — all four combinations of advertise_private_ips × external_ip_only × len(ips) > 0.

Related

  • Refs Proposal: opt-in support for advertising both host and srflx ICE candidates (pion v4.2.0 Append mode) livekit#4487
  • Sibling PR (same issue): feat/skip-external-ip-validation — independent, but touches overlapping file regions in pkg/rtcconfig/config.go and pkg/rtcconfig/webrtc_config_test.go. Resolves trivially; happy to rebase whichever lands second.
  • A companion livekit-side wiring PR (config-sample.yaml + unmarshal tests + go.mod bump) will follow once this and the sibling are merged and a release is cut, to keep livekit buildable from a clean checkout at every commit.

@CLAassistant

CLAassistant commented May 2, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@boks1971 boks1971 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lgtm! Thank you for doing this!

@lccool6683
lccool6683 force-pushed the feat/advertise-private-ips branch from 2955c2b to e30e64a Compare May 3, 2026 08:38
Introduce a new opt-in RTCConfig field AdvertisePrivateIPs (default false).
When set together with use_external_ip, NewWebRTCConfig publishes both the
private host candidate (the local IP) and the STUN-derived public IP as a
server-reflexive candidate, instead of replacing the host candidate with
the public IP.

This unblocks hybrid Kubernetes topologies where some peers reach LiveKit
via cluster-internal routing on the private IP while others reach it via
the public IP. The config has no effect when use_external_ip is false or
external_ip_only is true.

Implementation
- chooseNAT1To1Mode picks (Replace+Host) by default and switches to
  (Append+Srflx) only when AdvertisePrivateIPs is true and STUN actually
  returned external mappings.
- SetNAT1To1AddressRewriteRulesAppendSrflx wires the new mode through to
  pion's webrtc.SetICEAddressRewriteRules in append mode.
- buildNAT1To1Rules is the shared rule builder. The catch-all rule (one
  entry per external IP, no Local) is emitted regardless of mode: pion's
  gatherCandidatesSrflxMapped opens its UDP socket on 0.0.0.0, and the
  rule lookup keyed on that wildcard address must hit the catch-all to
  publish the correct external IP. Without it, srflx candidates would
  fall back to advertising 0.0.0.0 as the base address.
- Self-mappings (External == Local) are skipped in append mode because
  publishing "<private> -> <private>" as srflx is meaningless and only
  inflates ICE check overhead.

Validation guards
RTCConfig.Validate() rejects two configuration combinations that would
silently break the patch path. validateAdvertisePrivateIPs runs before
the port-defaulting block so operator typos (e.g. setting only
port_range_end) are caught with their original values rather than
silently masked by the production defaults.
- ForceTCP: rejected because pion's gatherCandidatesSrflxMapped skips
  TCP network types, so the public IP would never be advertised in
  TCP-only mode.
- Anything other than full port-range mode (single-port udp_port,
  partial port range with only one bound set, or unbounded UDP gather):
  rejected because pion's srflx mapped gather opens its own UDP listener
  via listenUDPInPortRange and falls back to an OS-ephemeral port when
  ICEPortRangeStart/End are not both non-zero. That port isn't covered
  by single-port firewall rules, so the published srflx candidate would
  be unreachable from external clients.

Operator note (also captured on the field comment): external clients on
networks that block UDP but allow outbound TCP/7881 lose the public TCP
host candidate they had under the legacy (Replace+Host) mode. Such
clients require TCP TURN at 443 to reach the SFU; ensure TURN/TLS is
provisioned before opting in.

Tests cover both legacy replace+host and the new append+srflx shape
(catch-all and self-mapping behavior) plus all combinations of the new
validation guards (force_tcp, single-port udp_port, partial port range
in either direction, no port config at all, and the valid full
port-range case).

Closes #4487
@lccool6683
lccool6683 force-pushed the feat/advertise-private-ips branch from e30e64a to 6ed90b3 Compare May 5, 2026 16:36
@boks1971

boks1971 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

@lccool6683 can this be closed as well given #88 ?

@lccool6683

Copy link
Copy Markdown
Author

yes! thnak you

@lccool6683 lccool6683 closed this Jun 11, 2026
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.

3 participants