Skip to content

ENGDESK-49495: Restrict ICE auto-change to negotiated candidates only - #513

Open
baloeng wants to merge 1 commit into
jira-tel-6622from
jira-engdesk-49495
Open

ENGDESK-49495: Restrict ICE auto-change to negotiated candidates only#513
baloeng wants to merge 1 commit into
jira-tel-6622from
jira-engdesk-49495

Conversation

@baloeng

@baloeng baloeng commented Feb 16, 2026

Copy link
Copy Markdown

Summary

Restricts ICE auto-change to only accept STUN Binding Requests from known ICE candidates (from SDP or trickle ICE). STUN from unknown IPs will still receive a response (required by ICE RFC), but will not trigger a change of RTP/DTLS destination.

Problem

A DTLS handshake failure occurred because:

  1. Voice SDK sent STUN from a TURN relay IP (64.16.248.199) not included in the SDP
  2. b2bua-rtc auto-changed RTP/DTLS destination to that IP
  3. Voice SDK responded to DTLS from its direct/srflx IP (102.218.8.246)
  4. DTLS handshake failed due to source IP mismatch (security requirement)

Root Cause Analysis

The client's SDP only contained:

  • 10.4.248.248:52332 (host - dropped by ACL)
  • 102.218.8.246:52332 (srflx - chosen)

No relay candidate was negotiated, yet the client sent STUN from 64.16.248.199:60158 with valid ICE credentials.

Fix

Before allowing auto-change, verify the STUN source IP matches a known candidate:

  • If cur_idx >= 0 (match found): proceed with auto-change
  • If cur_idx < 0 (no match): log warning, skip auto-change, still send STUN response

Testing

  • Verified fix prevents auto-change from unknown IPs
  • Verify existing network reconnection scenarios still work (ENGDESK-48822)

Related

When receiving a STUN Binding Request from an IP not in the negotiated
ICE candidates (from SDP or trickle ICE), skip the auto-change of
RTP/DTLS destination while still sending the required STUN response.

This prevents DTLS handshake failures caused by path mismatch when:
1. Client sends STUN from an unadvertised relay IP
2. b2bua-rtc auto-changes to that IP
3. Client responds to DTLS from its original/direct IP
4. DTLS fails due to source IP mismatch

The fix checks if the STUN source matches any known candidate (cur_idx >= 0)
before allowing the destination change. Unknown sources are logged as warnings.

Root cause: Voice SDK sent STUN from TURN relay IP (64.16.248.199) that was
not included in the SDP, then responded to DTLS from direct IP (102.218.8.246).

@greptile-apps greptile-apps 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.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps

greptile-apps Bot commented Feb 16, 2026

Copy link
Copy Markdown
Additional Comments (1)

src/switch_rtp.c
This loop only checks IP address (from_host) but not port. If a client sends STUN from the same IP with a different port (which is exactly the scenario in the PR description where relay used 64.16.248.199:60158), this will incorrectly match and set cur_idx >= 0, allowing the unwanted auto-change.

The earlier loop at lines 1565-1579 correctly checks both IP and port:

if (!strcmp(...con_addr, from_host) && ...con_port == from_port)

This needs the port check added:

				for (i = 0; i < ice->ice_params->cand_idx[ice->proto]; i++) {
					if (!strcmp(ice->ice_params->cands[i][ice->proto].con_addr, from_host) && ice->ice_params->cands[i][ice->proto].con_port == from_port) {
						cur_idx = i;
					}
				}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/switch_rtp.c
Line: 1669:1673

Comment:
This loop only checks IP address (`from_host`) but not port. If a client sends STUN from the same IP with a different port (which is exactly the scenario in the PR description where relay used `64.16.248.199:60158`), this will incorrectly match and set `cur_idx >= 0`, allowing the unwanted auto-change.

The earlier loop at lines 1565-1579 correctly checks both IP and port:
```c
if (!strcmp(...con_addr, from_host) && ...con_port == from_port)
```

This needs the port check added:

```suggestion
				for (i = 0; i < ice->ice_params->cand_idx[ice->proto]; i++) {
					if (!strcmp(ice->ice_params->cands[i][ice->proto].con_addr, from_host) && ice->ice_params->cands[i][ice->proto].con_port == from_port) {
						cur_idx = i;
					}
				}
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/switch_rtp.c
/* ENGDESK-49495: Only allow auto-change if source IP matches a known ICE candidate.
* This prevents DTLS path mismatch when STUN arrives from an IP not in the negotiated SDP.
* The STUN response is still sent (required by ICE), but we don't change the RTP/DTLS destination. */
if (cur_idx < 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

cur_idx is assigned only in the candidate-search loop inside the else if (!do_adj …)elapsed_adj > 1000 block — i.e. only on HIT 3/4/5. When do_adj is set earlier by HIT 1 (adj_window) or HIT 2 (switch_cmp_addr same-IP/diff-port, or use_candidate && !is_relay nomination), that loop never runs and cur_idx stays -1, so this guard unconditionally skips auto-change on those paths regardless of whether the source is a known candidate. That's very likely the ENGDESK-48822 reconnection regression. Suggest hoisting the candidate search so cur_idx is computed before this check on every adjust path.

Comment thread src/switch_rtp.c
rtp_type(rtp_session), is_rtcp ? "rtcp" : "rtp",
from_host, from_port, host2, port2);
} else {
ice->missed_count = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

missed_count, rready, and last_ok (further down) were previously set on every do_adj; moving them inside the cur_idx≥0 branch means a peer that keeps sending STUN from an unknown IP never refreshes liveness → can trip ICE timeout/teardown even though we're still answering its binding requests. Suggest keeping rready/last_ok outside this branch and gating only chosen[] + switch_rtp_change_ice_dest.

Comment thread src/switch_rtp.c
* The STUN response is still sent (required by ICE), but we don't change the RTP/DTLS destination. */
if (cur_idx < 0) {
switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_WARNING,
"Skipping auto-change for %s stun/%s/dtls - source %s:%u is not a negotiated ICE candidate (current: %s:%u)\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LOG_WARNING fires on every skipped binding request; STUN retransmits sub-second, so a sustained unknown-IP source will spam the logs. Suggest DEBUG, or log once per source.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants