Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion cli/command/swarm/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/pem"
"errors"
"fmt"
"net"
"os"
"strings"
"time"
Expand Down Expand Up @@ -74,7 +75,15 @@ func (*NodeAddrOption) Type() string {

// Value returns the value of this option as addr:port
func (a *NodeAddrOption) Value() string {
return strings.TrimPrefix(a.addr, "tcp://")
addr := strings.TrimPrefix(a.addr, "tcp://")
host, port, err := net.SplitHostPort(addr)
if err != nil {
return addr
}
// ParseTCPAddr returns a URL-safe zone identifier, but net.Listen expects
// the raw percent sign in an IPv6 zone.
host = strings.ReplaceAll(host, "%25", "%")
return net.JoinHostPort(host, port)
}

// NewNodeAddrOption returns a new node address option
Expand Down
6 changes: 6 additions & 0 deletions cli/command/swarm/opts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ func TestNodeAddrOptionSetHostOnlyIPv6(t *testing.T) {
assert.Check(t, is.Equal("[::1]:2377", opt.Value()))
}

func TestNodeAddrOptionSetHostAndPortIPv6Zone(t *testing.T) {
opt := NewListenAddrOption()
assert.NilError(t, opt.Set("tcp://[fe80::1%25lo]:5555"))
assert.Check(t, is.Equal("[fe80::1%lo]:5555", opt.Value()))
}

func TestNodeAddrOptionSetPortOnly(t *testing.T) {
opt := NewListenAddrOption()
assert.NilError(t, opt.Set(":4545"))
Expand Down
4 changes: 4 additions & 0 deletions opts/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
return "", fmt.Errorf("invalid bind address format: %s", tryAddr)
}

// url.Parse unescapes the zone identifier in an IPv6 address. Re-escape
// its percent sign when formatting the address as a URL, otherwise the
// returned address cannot be parsed again.
host = strings.ReplaceAll(host, "%", "%25")
return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil
}

Expand Down
11 changes: 11 additions & 0 deletions opts/hosts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package opts

import (
"net"
"net/url"
"testing"

"gotest.tools/v3/assert"
Expand Down Expand Up @@ -135,6 +136,16 @@ func TestParseTCP(t *testing.T) {
}
}

func TestParseTCPAddrIPv6Zone(t *testing.T) {
addr, err := ParseTCPAddr("tcp://[fe80::1%25lo]:5555", "tcp://127.0.0.1:2376")
assert.NilError(t, err)
assert.Check(t, is.Equal("tcp://[fe80::1%25lo]:5555", addr))

u, err := url.Parse(addr)
assert.NilError(t, err)
assert.Check(t, is.Equal("fe80::1%lo", u.Hostname()))
}

func TestParseInvalidUnixAddrInvalid(t *testing.T) {
if _, err := parseSimpleProtoAddr("unix", "tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "invalid proto, expected unix: tcp://127.0.0.1" {
t.Fatalf("Expected an error, got %v", err)
Expand Down