diff --git a/libnetwork/types/types.go b/libnetwork/types/types.go index f851d6fbb7..5968545ba5 100644 --- a/libnetwork/types/types.go +++ b/libnetwork/types/types.go @@ -145,7 +145,12 @@ func (p *PortBinding) String() string { return ret } -// FromString reads the PortBinding structure from string +// FromString reads the PortBinding structure from string s. +// String s is a triple of "protocol/containerIP:port/hostIP:port" +// containerIP and hostIP can be in dotted decimal ("192.0.2.1") or IPv6 ("2001:db8::68") form. +// Zoned addresses ("169.254.0.23%eth0" or "fe80::1ff:fe23:4567:890a%eth0") are not supported. +// If string s is incorrectly formatted or the IP addresses or ports cannot be parsed, FromString +// returns an error. func (p *PortBinding) FromString(s string) error { ps := strings.Split(s, "/") if len(ps) != 3 { @@ -167,21 +172,19 @@ func (p *PortBinding) FromString(s string) error { } func parseIPPort(s string) (net.IP, uint16, error) { - pp := strings.Split(s, ":") - if len(pp) != 2 { - return nil, 0, BadRequestErrorf("invalid format: %s", s) - } - - var ip net.IP - if pp[0] != "" { - if ip = net.ParseIP(pp[0]); ip == nil { - return nil, 0, BadRequestErrorf("invalid ip: %s", pp[0]) - } - } - - port, err := strconv.ParseUint(pp[1], 10, 16) + hoststr, portstr, err := net.SplitHostPort(s) if err != nil { - return nil, 0, BadRequestErrorf("invalid port: %s", pp[1]) + return nil, 0, err + } + + ip := net.ParseIP(hoststr) + if ip == nil { + return nil, 0, BadRequestErrorf("invalid ip: %s", hoststr) + } + + port, err := strconv.ParseUint(portstr, 10, 16) + if err != nil { + return nil, 0, BadRequestErrorf("invalid port: %s", portstr) } return ip, uint16(port), nil diff --git a/libnetwork/types/types_test.go b/libnetwork/types/types_test.go index 1c5f27547a..b487099079 100644 --- a/libnetwork/types/types_test.go +++ b/libnetwork/types/types_test.go @@ -2,6 +2,7 @@ package types import ( "flag" + "github.com/stretchr/testify/require" "net" "testing" ) @@ -26,21 +27,60 @@ func TestTransportPortConv(t *testing.T) { } func TestTransportPortBindingConv(t *testing.T) { - sform := "tcp/172.28.30.23:80/112.0.43.56:8001" - pb := &PortBinding{ - Proto: TCP, - IP: net.IPv4(172, 28, 30, 23), - Port: uint16(80), - HostIP: net.IPv4(112, 0, 43, 56), - HostPort: uint16(8001), + input := []struct { + sform string + pb PortBinding + shouldFail bool + }{ + { // IPv4 -> IPv4 + sform: "tcp/172.28.30.23:80/112.0.43.56:8001", + pb: PortBinding{ + Proto: TCP, + IP: net.IPv4(172, 28, 30, 23), + Port: uint16(80), + HostIP: net.IPv4(112, 0, 43, 56), + HostPort: uint16(8001), + }, + }, + { // IPv6 -> IPv4 + sform: "tcp/[2001:db8::1]:80/112.0.43.56:8001", + pb: PortBinding{ + Proto: TCP, + IP: net.IP{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + Port: uint16(80), + HostIP: net.IPv4(112, 0, 43, 56), + HostPort: uint16(8001), + }, + }, + { // IPv4inIPv6 -> IPv4 + sform: "tcp/[::ffff:172.28.30.23]:80/112.0.43.56:8001", + pb: PortBinding{ + Proto: TCP, + IP: net.IPv4(172, 28, 30, 23), + Port: uint16(80), + HostIP: net.IPv4(112, 0, 43, 56), + HostPort: uint16(8001), + }, + }, + { // IPv4 -> IPv4 zoned + sform: "tcp/172.28.30.23:80/169.254.0.23%eth0:8001", + shouldFail: true, + }, + { // IPv4 -> IPv6 zoned + sform: "tcp/172.28.30.23:80/[fe80::1ff:fe23:4567:890a%eth0]:8001", + shouldFail: true, + }, } - rc := new(PortBinding) - if err := rc.FromString(sform); err != nil { - t.Fatal(err) - } - if !pb.Equal(rc) { - t.Fatalf("FromString() method failed") + for _, in := range input { + rc := new(PortBinding) + err := rc.FromString(in.sform) + if in.shouldFail { + require.Error(t, err, "Unexpected success parsing %s", in.sform) + } else { + require.NoError(t, err) + require.Equal(t, in.pb, *rc, "input %s: expected %#v, got %#v", in.sform, in.pb, rc) + } } }