From 462f96a4ded455414222467c85d14c4748b3de8d Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Fri, 6 Dec 2024 15:43:06 -0500 Subject: [PATCH] discovery: correctly handle IPv6 addresses from go-discover Nomad sets a default port when resolving server addresses that don't have one. When we get a "bare" IPv6 address without a port, we end up with an unexpected error "too many colons in address" when we try to split the address and host, because the standard library function expects IPv6 addresses to be wrapped in brackets as recommended by RFC5952. User-configured addresses avoid this problem by accepting IP address and port as separate configuration values, but go-discover emits "bare" IPv6 addresses without a port in IPv6 environments. Fix this by adding brackets to IPv6 addresses when we get the "too many colons" error from the stdlib. This will still give erroneous results if the address includes the port but is missing brackets, but there's no way to unambiguously parse that address. Ref: https://www.rfc-editor.org/rfc/rfc5952 Fixes: https://github.com/hashicorp/nomad/issues/24608 --- .changelog/24649.txt | 3 +++ client/rpc.go | 20 ++++++++++++++++++-- client/rpc_test.go | 12 +++++++++--- 3 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 .changelog/24649.txt diff --git a/.changelog/24649.txt b/.changelog/24649.txt new file mode 100644 index 00000000000..1b1e4701b18 --- /dev/null +++ b/.changelog/24649.txt @@ -0,0 +1,3 @@ +```release-note:bug +discovery: Fixed a bug where IPv6 addresses would not be accepted from cloud autojoin +``` diff --git a/client/rpc.go b/client/rpc.go index 5427bb76254..bfdf051c121 100644 --- a/client/rpc.go +++ b/client/rpc.go @@ -445,14 +445,30 @@ func (c *Client) handleStreamingConn(conn net.Conn) { // net.Addr or an error. func resolveServer(s string) (net.Addr, error) { const defaultClientPort = "4647" // default client RPC port + host, port, err := net.SplitHostPort(s) if err != nil { - if strings.Contains(err.Error(), "missing port") { + switch { + case strings.Contains(err.Error(), "missing port"): // with IPv6 addresses the `host` variable will have brackets // removed, so send the original value thru again with only the // correct port suffix return resolveServer(s + ":" + defaultClientPort) - } else { + case strings.Contains(err.Error(), "too many colons"): + // note: we expect IPv6 address strings to be RFC5952 compliant to + // disambiguate port numbers from the address. Because the port number + // is typically 4 decimal digits, the same size as an IPv6 address + // segment, there's no way to disambiguate this. See + // https://www.rfc-editor.org/rfc/rfc5952 + ip := net.ParseIP(s) + if ip.To4() == nil && ip.To16() != nil { + if !strings.HasPrefix(s, "[") { + return resolveServer("[" + s + "]:" + defaultClientPort) + } + } + return nil, err + + default: return nil, err } } else if port == "" { diff --git a/client/rpc_test.go b/client/rpc_test.go index 3762f6108ca..5b4e26b1116 100644 --- a/client/rpc_test.go +++ b/client/rpc_test.go @@ -130,9 +130,15 @@ func Test_resolveServer(t *testing.T) { expectErr string }{ { - name: "ipv6 no brackets", - addr: "2001:db8::1", - expectErr: "address 2001:db8::1: too many colons in address", + name: "ipv6 no brackets", + addr: "2001:db8::1", + expect: "[2001:db8::1]:4647", + }, + { + // expected bad result + name: "ambiguous ipv6 no brackets with port", + addr: "2001:db8::1:4647", + expect: "[2001:db8::1:4647]:4647", }, { name: "ipv6 no port",