Skip to content

fix(options): keep single brackets for IPv6 hosts without a port in ParseURL#3882

Open
sueun-dev wants to merge 1 commit into
redis:masterfrom
sueun-dev:fix/parseurl-ipv6-brackets
Open

fix(options): keep single brackets for IPv6 hosts without a port in ParseURL#3882
sueun-dev wants to merge 1 commit into
redis:masterfrom
sueun-dev:fix/parseurl-ipv6-brackets

Conversation

@sueun-dev

@sueun-dev sueun-dev commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

ParseURL and ParseClusterURL mangle a bracketed IPv6 host when the URL omits the port.

getHostPortWithDefaults calls net.SplitHostPort(u.Host), which fails with missing port for a literal such as [::1]. The error branch falls back to host = u.Host, keeping the brackets, and the caller then runs net.JoinHostPort(host, "6379"), which wraps the already-bracketed host a second time:

ParseURL("redis://[::1]")         // Addr = "[[::1]]:6379"
ParseURL("redis://[2001:db8::1]") // Addr = "[[2001:db8::1]]:6379"

The double-bracketed address can't be dialed. For rediss:// it also leaves the TLS ServerName bracketed ([::1] instead of ::1), so the handshake uses an invalid server name. A host with an explicit port (redis://[::1]:6379) is unaffected, because there SplitHostPort succeeds.

Fix

Use u.Hostname() / u.Port(). They strip the IPv6 brackets and report a missing port as an empty string, so the existing defaulting still applies and JoinHostPort adds exactly one pair of brackets. The result is identical to the old code for every non-IPv6 case.

Tests

Added IPv6 rows to TestParseURL and a TestParseURLIPv6TLSServerName check for the SNI. Both fail before the change (got "[[::1]]:6379", want "[::1]:6379") and pass after. gofmt, go vet ./... and go build ./... are clean.


Note

Low Risk
Small, localized URL parsing fix with targeted tests; behavior for non-IPv6 URLs is intended to stay the same.

Overview
Fixes broken dial addresses for Redis URLs that use a bracketed IPv6 host without an explicit port (e.g. redis://[::1]). getHostPortWithDefaults now uses url.URL.Hostname() and Port() instead of net.SplitHostPort, so the host is unbracketed before net.JoinHostPort applies the default port 6379. That stops double-bracket forms like [[::1]]:6379 that could not be dialed.

The same helper is used by cluster and sentinel URL parsing, so those entry points get the same IPv6 behavior. For rediss://, TLS ServerName is set from the unbracketed host (e.g. 2001:db8::1 instead of [2001:db8::1]).

Tests add IPv6 cases to TestParseURL and TestParseURLIPv6TLSServerName for the SNI name.

Reviewed by Cursor Bugbot for commit b199183. Bugbot is set up for automated code reviews on this repo. Configure here.

…arseURL

getHostPortWithDefaults calls net.SplitHostPort(u.Host), which fails with
"missing port" for a bracketed IPv6 literal such as "[::1]". The error path
falls back to host = u.Host, keeping the brackets, and the caller then runs
net.JoinHostPort(host, "6379"), which wraps the already-bracketed host again:

    ParseURL("redis://[::1]")         -> Addr "[[::1]]:6379"
    ParseURL("redis://[2001:db8::1]") -> Addr "[[2001:db8::1]]:6379"

The double-bracketed address cannot be dialed. For rediss:// it also leaves
the TLS ServerName bracketed ("[::1]" instead of "::1"). A host with an
explicit port is unaffected because SplitHostPort succeeds there.

Use u.Hostname()/u.Port(), which strip the IPv6 brackets and report a missing
port as an empty string, so the existing defaulting still applies and
JoinHostPort adds exactly one pair of brackets. The result is identical to the
old code for every non-IPv6 case.
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