Skip to content

[client, management, relay, misc] Use net.JoinHostPort for IPv6-safe host:port handling#5836

Merged
lixmal merged 1 commit intoproto-ipv6-overlayfrom
client-ipv6-hostport-fixes
Apr 10, 2026
Merged

[client, management, relay, misc] Use net.JoinHostPort for IPv6-safe host:port handling#5836
lixmal merged 1 commit intoproto-ipv6-overlayfrom
client-ipv6-hostport-fixes

Conversation

@lixmal
Copy link
Copy Markdown
Collaborator

@lixmal lixmal commented Apr 9, 2026

Describe your changes

Replace fmt.Sprintf("%s:%d", host, port) and strings.Split(addr, ":") with net.JoinHostPort/net.SplitHostPort across the codebase. Without this, IPv6 addresses produce malformed strings like 2001:db8::1:3478 instead of [2001:db8::1]:3478.

  • Replace host:port construction with net.JoinHostPort in SSH port forwarding, relay TURN probing, DNS nameserver URL parsing, STUN URI construction, management URL construction, reverse proxy service URLs, and packet tracer display
  • Replace strings.Split on : with net.SplitHostPort in rosenpass UDP port parsing
  • Fix anonymizer URI reconstruction to use net.JoinHostPort so bracketed IPv6 addresses are preserved through AnonymizeString
  • Add tests for ConnKey.String(), ICMPConnKey.String(), findRandomAvailableUDPPort, toServerNSList with IPv6, and AnonymizeString with IPv6 STUN/HTTPS URIs

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Summary by CodeRabbit

  • Bug Fixes

    • Improved IPv6 address handling and formatting consistency across the application.
    • Enhanced proper host:port combination formatting for network addresses, particularly benefiting IPv6 support.
  • Tests

    • Added comprehensive test coverage for IPv6 address anonymization and formatting in various contexts.
    • Extended test suite for port parsing and address string output validation.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 9, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2c493072-c566-4524-b776-f8228c4f72a9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch client-ipv6-hostport-fixes

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.

@lixmal lixmal force-pushed the client-ipv6-hostport-fixes branch from 34a8682 to 331dfca Compare April 9, 2026 09:57
@lixmal lixmal force-pushed the client-ipv6-hostport-fixes branch from 331dfca to 12752b3 Compare April 9, 2026 10:41
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
client/ssh/server/server.go (1)

922-934: Consider reusing a single hostPort value across all branches.

This removes repeated formatting and keeps log output construction consistent in one place.

♻️ Suggested refactor
+	hostPort := net.JoinHostPort(payload.Host, strconv.Itoa(int(payload.Port)))
 	if !allowLocal {
-		logger.Warnf("local port forwarding denied for %s: disabled", net.JoinHostPort(payload.Host, strconv.Itoa(int(payload.Port))))
+		logger.Warnf("local port forwarding denied for %s: disabled", hostPort)
 		_ = newChan.Reject(cryptossh.Prohibited, "local port forwarding disabled")
 		return
 	}
 
 	if err := s.checkPortForwardingPrivileges(ctx, "local", payload.Port); err != nil {
-		logger.Warnf("local port forwarding denied for %s: %v", net.JoinHostPort(payload.Host, strconv.Itoa(int(payload.Port))), err)
+		logger.Warnf("local port forwarding denied for %s: %v", hostPort, err)
 		_ = newChan.Reject(cryptossh.Prohibited, "insufficient privileges")
 		return
 	}
 
-	hostPort := net.JoinHostPort(payload.Host, strconv.Itoa(int(payload.Port)))
 	forwardAddr := "-L " + hostPort
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/ssh/server/server.go` around lines 922 - 934, Extract hostPort :=
net.JoinHostPort(payload.Host, strconv.Itoa(int(payload.Port))) before the
permission checks and reuse it in the subsequent log messages, Reject calls, and
when building forwardAddr (replace the inline net.JoinHostPort(...) and the
later hostPort assignment); update logger.Warnf lines and forwardAddr := "-L
"+hostPort to use this single hostPort variable and keep formatting consistent.
management/server/http/handlers/dns/nameservers_handler_test.go (1)

250-256: Add a bracketed IPv6 test case for full regression coverage.

Current IPv6 coverage validates unbracketed input only; please add a case like "[2001:4860:4860::8888]" to explicitly guard bracket-handling behavior too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/http/handlers/dns/nameservers_handler_test.go` around lines
250 - 256, Add a new test case in nameservers_handler_test.go alongside the
existing IPv6 case that uses a bracketed IPv6 string to validate bracket
handling: add a struct with name "IPv6 (bracketed)" and input
[]api.Nameserver{{Ip: "[2001:4860:4860::8888]", NsType: "udp", Port: 53}} and
set expectIP to netip.MustParseAddr("2001:4860:4860::8888") so the same parsing
assertion used by the existing IPv6 test verifies bracket stripping; place it
near the existing IPv6 test entry to ensure full regression coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/ssh/server/port_forwarding.go`:
- Around line 315-316: The listener is being stored under the requested port
(payload.Port) which can be zero; change storeRemoteForwardListener to use the
actual allocated port instead so cancel-tcpip-forward lookups match—construct
the forward key with forwardKey(net.JoinHostPort(payload.Host,
strconv.Itoa(int(actualPort)))) (using the actualPort returned/assigned by the
listener) and call s.storeRemoteForwardListener with that key and ln, ensuring
any later cancel handling that derives keys from the allocated port will find
the stored listener.

---

Nitpick comments:
In `@client/ssh/server/server.go`:
- Around line 922-934: Extract hostPort := net.JoinHostPort(payload.Host,
strconv.Itoa(int(payload.Port))) before the permission checks and reuse it in
the subsequent log messages, Reject calls, and when building forwardAddr
(replace the inline net.JoinHostPort(...) and the later hostPort assignment);
update logger.Warnf lines and forwardAddr := "-L "+hostPort to use this single
hostPort variable and keep formatting consistent.

In `@management/server/http/handlers/dns/nameservers_handler_test.go`:
- Around line 250-256: Add a new test case in nameservers_handler_test.go
alongside the existing IPv6 case that uses a bracketed IPv6 string to validate
bracket handling: add a struct with name "IPv6 (bracketed)" and input
[]api.Nameserver{{Ip: "[2001:4860:4860::8888]", NsType: "udp", Port: 53}} and
set expectIP to netip.MustParseAddr("2001:4860:4860::8888") so the same parsing
assertion used by the existing IPv6 test verifies bracket stripping; place it
near the existing IPv6 test entry to ensure full regression coverage.
🪄 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: ae3aad38-519a-4e5c-b6eb-5f9a80e28af9

📥 Commits

Reviewing files that changed from the base of the PR and between 0cc90e2 and 12752b3.

📒 Files selected for processing (21)
  • client/anonymize/anonymize.go
  • client/anonymize/anonymize_test.go
  • client/cmd/ssh.go
  • client/firewall/uspfilter/conntrack/common_test.go
  • client/firewall/uspfilter/conntrack/icmp.go
  • client/firewall/uspfilter/conntrack/icmp_test.go
  • client/firewall/uspfilter/tracer.go
  • client/internal/profilemanager/config.go
  • client/internal/relay/relay.go
  • client/internal/rosenpass/manager.go
  • client/internal/rosenpass/manager_test.go
  • client/ssh/proxy/proxy.go
  • client/ssh/server/port_forwarding.go
  • client/ssh/server/server.go
  • combined/cmd/config.go
  • management/internals/modules/reverseproxy/service/manager/manager.go
  • management/server/http/handlers/dns/nameservers_handler.go
  • management/server/http/handlers/dns/nameservers_handler_test.go
  • relay/test/benchmark_test.go
  • relay/testec2/turn_allocator.go
  • upload-server/server/s3_test.go

Comment on lines +315 to 316
key := forwardKey(net.JoinHostPort(payload.Host, strconv.Itoa(int(payload.Port))))
s.storeRemoteForwardListener(key, ln)
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.

⚠️ Potential issue | 🟠 Major

Use the allocated port for forwardKey to keep cancel lookups consistent.

When remote forwarding requests port 0, the server allocates actualPort. Storing the listener under payload.Port (0) can break cancel-tcpip-forward lookup when cancellation uses the allocated port.

🐛 Proposed fix
-	key := forwardKey(net.JoinHostPort(payload.Host, strconv.Itoa(int(payload.Port))))
+	key := forwardKey(net.JoinHostPort(payload.Host, strconv.Itoa(int(actualPort))))
 	s.storeRemoteForwardListener(key, ln)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
key := forwardKey(net.JoinHostPort(payload.Host, strconv.Itoa(int(payload.Port))))
s.storeRemoteForwardListener(key, ln)
key := forwardKey(net.JoinHostPort(payload.Host, strconv.Itoa(int(actualPort))))
s.storeRemoteForwardListener(key, ln)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/ssh/server/port_forwarding.go` around lines 315 - 316, The listener is
being stored under the requested port (payload.Port) which can be zero; change
storeRemoteForwardListener to use the actual allocated port instead so
cancel-tcpip-forward lookups match—construct the forward key with
forwardKey(net.JoinHostPort(payload.Host, strconv.Itoa(int(actualPort)))) (using
the actualPort returned/assigned by the listener) and call
s.storeRemoteForwardListener with that key and ln, ensuring any later cancel
handling that derives keys from the allocated port will find the stored
listener.

@lixmal lixmal merged commit f484835 into proto-ipv6-overlay Apr 10, 2026
45 checks passed
@lixmal lixmal deleted the client-ipv6-hostport-fixes branch April 10, 2026 01:11
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.

2 participants