Skip to content

fix(remote): normalize localhost to 127.0.0.1 for IPv4-only remote servers#245

Merged
BANANASJIM merged 2 commits into
masterfrom
fix/proxy-localhost-ipv4
Jun 10, 2026
Merged

fix(remote): normalize localhost to 127.0.0.1 for IPv4-only remote servers#245
BANANASJIM merged 2 commits into
masterfrom
fix/proxy-localhost-ipv4

Conversation

@BANANASJIM

@BANANASJIM BANANASJIM commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

rdc open <capture> --proxy localhost:39920 stalled indefinitely at uploading: 0% on dual-stack hosts: localhost resolves to ::1 (IPv6) first, while renderdoccmd remoteserver binds IPv4 only (verified via ss -tlnp: listener on 0.0.0.0:39920, none on ::1). RenderDoc's native connection layer does not fall back to IPv4, so the upload never progresses and there is no error.

This change normalizes the literal hostname localhost (case-insensitive) to 127.0.0.1 everywhere a host string reaches a RenderDoc remote API.

Seams covered

  • _normalize_remote_host helper in remote_core.py, applied in parse_url and build_conn_url (the latter also closes a saved-state path: a stale localhost_<port>.json written by an older version would otherwise resurrect the un-normalized host through rdc remote list/capture).
  • daemon_server._load_remote_replay normalizes the incoming --remote-url up front (with an adb:// guard), so both CreateRemoteServerConnection and the rdc status display use the normalized value.
  • Hardcoded "localhost" literals replaced in android.py (remote-server connect, CreateTargetControl) and capture_core.py (EnumerateRemoteTargets).
  • capture_control._connect normalizes --host for attach/capture-trigger/capture-list/capture-copy (review-found gap; covers the default and explicit --host localhost).

Untouched by design: ::1 and bracketed IPv6 literals, real hostnames (mylocalhost, localhost.example.com), adb:// URLs, and split-mode --connect (Python's socket.create_connection iterates getaddrinfo with immediate ECONNREFUSED fallback, so it does not stall).

Testing

  • 19 unit test additions/updates across test_remote_core.py, test_daemon_server_unit.py, test_android_commands.py, test_capture_core.py, test_capture_control.py — asserting the host actually passed to the (mocked) RenderDoc APIs, plus an 8-case normalization boundary matrix (LOCALHOST, mylocalhost, localhost.example.com, [::1], etc.).
  • Real-GPU verification (AMD RX 7600, RADV): pre-fix base reproduces the uploading: 0% stall with --proxy localhost:39920; with this fix the same command opens the session, rdc status shows 127.0.0.1:39920, remote draws and depth export work end-to-end; explicit --proxy 127.0.0.1 unchanged; no localhost_*.json state ghost created.
  • Full suite: 2992 passed; ruff + mypy strict clean.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed rdc hanging when using proxy URLs with localhost (e.g., localhost:39920) on dual-stack hosts by normalizing localhost to 127.0.0.1 for IPv4 loopback resolution.
  • Tests

    • Added comprehensive unit tests verifying localhost normalization across proxy connections, remote URL parsing, daemon server handling, and capture control paths.

…rvers

rdc open --proxy localhost:39920 stalled indefinitely at "uploading: 0%"
on dual-stack hosts: localhost resolves to ::1 first, but renderdoccmd
remoteserver binds IPv4-only (0.0.0.0) and never listens on ::1, so the
RenderDoc C++ client blocks forever with no error.

Add _normalize_remote_host (literal "localhost", case-insensitive ->
127.0.0.1; ::1, IPv6 literals, and real hostnames untouched) and apply it
at every seam where a user host string reaches a RenderDoc API:
- top of _load_remote_replay (covers --proxy and android open; also fixes
  state.remote_url shown by rdc status; guards adb:// / protocol URLs)
- parse_url (rdc remote connect/list/capture/setup)
- build_conn_url (closes the saved-state ghost path: a pre-fix state file
  keyed on localhost is rewritten at URL assembly)
- hardcoded literals in android.py and capture_core.py

State files previously keyed on localhost become stale after upgrade;
rdc remote disconnect && rdc remote connect 127.0.0.1:PORT cleans them up.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements a fix for rdc hanging on dual-stack hosts when proxying through localhost:PORT by normalizing localhost to 127.0.0.1 across remote connection URL paths. The change prevents IPv6-first resolution from stalling RenderDoc's IPv4-only remote connection API. Implementation includes a reusable normalization helper, integration at daemon replay, capture control, and direct replacements in Android and capture core, with comprehensive unit test coverage.

Changes

Localhost normalization for proxy connections

Layer / File(s) Summary
Specification and planning documentation
openspec/changes/2026-06-10-proxy-localhost-ipv4/proposal.md, tasks.md, test-plan.md
Proposal traces the root cause (unnormalized localhost hitting RenderDoc C++ remote API on dual-stack systems), specifies the _normalize_remote_host helper and its integration points, documents state-file implications, and confirms split-mode behavior is unaffected. Task and test plans enumerate implementation checkpoints and verification steps.
Core normalization helper and URL utilities
src/rdc/remote_core.py, tests/unit/test_remote_core.py
_normalize_remote_host rewrites case-insensitive localhost to 127.0.0.1 while preserving IPv4, IPv6, and other hostnames. Integration in build_conn_url and parse_url ensures all URL assembly and parsing paths use normalized hosts. Unit tests verify localhost variants normalize correctly, non-localhost addresses pass through, and state-file keys use 127.0.0.1_PORT.json format.
Daemon server replay URL normalization
src/rdc/daemon_server.py, tests/unit/test_daemon_server_unit.py
_load_remote_replay normalizes localhost:PORT remote URLs before passing to RenderDoc remote connection API, with debug logging. Unit test confirms both the connection URL and persisted state.remote_url use 127.0.0.1:PORT.
Capture control target connection normalization
src/rdc/commands/capture_control.py, tests/unit/test_capture_control.py
_connect normalizes the provided host before creating TargetControl connections. Unit tests verify default host and explicit LOCALHOST normalize to 127.0.0.1, while non-local hosts like 192.168.1.50 pass through unchanged.
Direct IPv4 loopback replacements in Android and capture core
src/rdc/commands/android.py, src/rdc/capture_core.py, tests/unit/test_android_commands.py, tests/unit/test_capture_core.py
Android setup and capture commands, and capture core target discovery directly use 127.0.0.1 instead of localhost for connection URLs. Updated tests assert IPv4 hosts are used in forwarded port setup, target control creation, and remote target enumeration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • BANANASJIM/rdc-cli#112: Both PRs touch the TargetControl connection flow in src/rdc/commands/capture_control.py—this PR adds localhost127.0.0.1 host normalization inside _connect, which directly affects the TargetControl commands introduced in PR #112.

Poem

🐰 A rabbit hops through IPv4 lanes,
Where localhost once caused endless pains,
Now 127.0.0.1 lights the way,
No IPv6 stalls to ruin the day!
The dual-stack dance is finally done,
Remote connections now flow as one. 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main change: normalizing the literal 'localhost' hostname to '127.0.0.1' for remote server connections to resolve IPv6-first dual-stack resolution issues.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/proxy-localhost-ipv4

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@BANANASJIM

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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 (2)
openspec/changes/2026-06-10-proxy-localhost-ipv4/proposal.md (2)

58-66: ⚡ Quick win

Add language identifier to fenced code block.

The code block should specify python after the opening backticks for proper syntax highlighting and to follow Markdown best practices.

📝 Proposed fix
-```
+```python
 def _normalize_remote_host(host: str) -> str:
     if host.lower() == "localhost":
🤖 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 `@openspec/changes/2026-06-10-proxy-localhost-ipv4/proposal.md` around lines 58
- 66, The fenced code block in proposal.md should include a language identifier
for proper syntax highlighting; update the block that contains the function
_normalize_remote_host(host: str) -> str to start with ```python instead of ```
so the snippet (including the function name _normalize_remote_host and its body)
is treated as Python code by Markdown renderers.

Source: Linters/SAST tools


80-80: ⚡ Quick win

Fix heading formatting.

Add a space after # for proper ATX-style heading syntax.

📝 Proposed fix
-**Option A** — normalize inside `parse_url` only, and also add a thin wrapper
+# Option A
+
+Normalize inside `parse_url` only, and also add a thin wrapper

Or if the bold formatting was intentional (not a heading):

-**Option A** — normalize inside `parse_url` only, and also add a thin wrapper
+**Option A** — Normalize inside `parse_url` only, and also add a thin wrapper
🤖 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 `@openspec/changes/2026-06-10-proxy-localhost-ipv4/proposal.md` at line 80, The
line beginning "`#4`, `#5`). Applying normalization there covers those paths with
one change." is being parsed as an ATX heading because there is no space after
the hash; fix by either adding a space after the hash to make a proper heading
(e.g., change "`#4`, `#5`)..." to "# 4, # 5)..." or, if the intent was not to create
a heading, escape or reformat the tokens (wrap "`#4`, `#5`" in backticks or bold
them) so they render as inline text rather than an ATX heading.

Source: Linters/SAST tools

🤖 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 `@openspec/changes/2026-06-10-proxy-localhost-ipv4/proposal.md`:
- Around line 58-66: The fenced code block in proposal.md should include a
language identifier for proper syntax highlighting; update the block that
contains the function _normalize_remote_host(host: str) -> str to start with
```python instead of ``` so the snippet (including the function name
_normalize_remote_host and its body) is treated as Python code by Markdown
renderers.
- Line 80: The line beginning "`#4`, `#5`). Applying normalization there covers
those paths with one change." is being parsed as an ATX heading because there is
no space after the hash; fix by either adding a space after the hash to make a
proper heading (e.g., change "`#4`, `#5`)..." to "# 4, # 5)..." or, if the intent
was not to create a heading, escape or reformat the tokens (wrap "`#4`, `#5`" in
backticks or bold them) so they render as inline text rather than an ATX
heading.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 58b4c9a6-df88-4007-bd00-67812cd71542

📥 Commits

Reviewing files that changed from the base of the PR and between 40f103f and 0dd0d23.

📒 Files selected for processing (13)
  • openspec/changes/2026-06-10-proxy-localhost-ipv4/proposal.md
  • openspec/changes/2026-06-10-proxy-localhost-ipv4/tasks.md
  • openspec/changes/2026-06-10-proxy-localhost-ipv4/test-plan.md
  • src/rdc/capture_core.py
  • src/rdc/commands/android.py
  • src/rdc/commands/capture_control.py
  • src/rdc/daemon_server.py
  • src/rdc/remote_core.py
  • tests/unit/test_android_commands.py
  • tests/unit/test_capture_control.py
  • tests/unit/test_capture_core.py
  • tests/unit/test_daemon_server_unit.py
  • tests/unit/test_remote_core.py

@BANANASJIM BANANASJIM merged commit 23b20cc into master Jun 10, 2026
17 checks passed
@BANANASJIM BANANASJIM deleted the fix/proxy-localhost-ipv4 branch June 10, 2026 08:32
BANANASJIM added a commit that referenced this pull request Jun 10, 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.

1 participant