Skip to content

opts: accept bracketed IPv6 host without a port in ParseTCPAddr - #7401

Open
Yeagerist0 wants to merge 2 commits into
docker:masterfrom
Yeagerist0:fix-parsetcpaddr-bare-ipv6
Open

Yeagerist0 wants to merge 2 commits into
docker:masterfrom
Yeagerist0:fix-parsetcpaddr-bare-ipv6

Conversation

@Yeagerist0

Copy link
Copy Markdown

Summary

docker -H tcp://[::1] (a bracketed IPv6 literal with no port) is rejected with invalid bind address format, while tcp://[::1]: and tcp://::1 are accepted and get the default port.

ParseTCPAddr adds the default port with net.JoinHostPort(u.Host, defaultPort). u.Host still has its brackets, and JoinHostPort adds its own, so the result was [[::1]]:2375. The fix strips the brackets before adding the default port. I added [::1] and [0:0:0:0:0:0:0:1] cases to TestParseDockerDaemonHost and [::1] / [::1]/path to TestParseTCP; they fail without the change and pass with it. I did not run it against a live daemon.

Release notes

Fix `-H`/`DOCKER_HOST` rejecting a bracketed IPv6 address without a port, such as `tcp://[::1]`.

Created with: Claude Code (Claude Sonnet 5.5). I used it to find the bug and draft the fix and tests; I reviewed the change and own it.

ParseTCPAddr appends the default port by calling net.JoinHostPort on u.Host. For a bracketed IPv6 literal without a port ("tcp://[::1]") u.Host still contains the brackets, so JoinHostPort produced "[[::1]]:2375" and the address was rejected as an invalid bind address, while "[::1]:" and "::1" were both accepted.

Strip the brackets before adding the default port, and add test cases.

Signed-off-by: Hitarth Jain <180412661+Yeagerist0@users.noreply.github.com>

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

I pulled ParseTCPAddr from af08d01 into a scratch program and ran it on some edge cases (default tcp://127.0.0.1:2375):

"[::1]"                  -> "tcp://[::1]:2375"
"tcp://[::1]"            -> "tcp://[::1]:2375"
"[::1]/path"             -> "tcp://[::1]:2375/path"
"::1"                    -> "tcp://[::1]:2375"       (unchanged)
"[fe80::1%25eth0]"       -> "tcp://[fe80::1%eth0]:2375"
"[]", "[[::1]]", "[localhost]", "[::1]x"  -> still rejected by url.Parse

So the fix does what it says and doesn't loosen validation. The bracket strip only runs on the fallback path, after url.Parse has already validated the IP literal.

If anyone suggests replacing the manual strip with u.Hostname(): that would break the unbracketed form, which currently works through this same fallback. url.Parse("tcp://::1") gives Host == "::1" but Hostname() == ":". The explicit [...] check is the safer choice. A short note in the comment might stop someone "simplifying" it later.

One thing I found that this PR doesn't touch (it happens with or without it): with a zone, the returned string isn't a valid URL anymore. The input needs %25 to get past url.Parse, but the output is built with fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path) (opts/hosts.go#L149). That emits the decoded %eth0, and parsing it again fails with invalid URL escape "%et". [fe80::1%25eth0]:5555 does the same on master. That probably belongs in its own issue, not in this PR.

u.Hostname() returns ":" for the unbracketed form ("tcp://::1"), which
also goes through this fallback.

Signed-off-by: Hitarth Jain <180412661+Yeagerist0@users.noreply.github.com>
@Yeagerist0

Copy link
Copy Markdown
Author

Thanks @by-ae, that's a useful check. I added the note about u.Hostname() in the latest commit.

For the zone case: agreed, it's a separate bug and it's already there on master. I'll open an issue for it instead of adding it to this PR.

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