diff --git a/nat/nat.go b/nat/nat.go index 1fbb13e..6595feb 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -34,17 +34,20 @@ type PortSet map[Port]struct{} // Port is a string containing port number and protocol in the format "80/tcp" type Port string -// NewPort creates a new instance of a Port given a protocol and port number +// NewPort creates a new instance of a Port given a protocol and port number or port range func NewPort(proto, port string) (Port, error) { // Check for parsing issues on "port" now so we can avoid having // to check it later on. - portInt, err := ParsePort(port) + portStartInt, portEndInt, err := ParsePortRange(port) if err != nil { return "", err } - return Port(fmt.Sprintf("%d/%s", portInt, proto)), nil + if portStartInt == portEndInt { + return Port(fmt.Sprintf("%d/%s", portStartInt, proto)), nil + } + return Port(fmt.Sprintf("%d-%d/%s", portStartInt, portEndInt, proto)), nil } // ParsePort parses the port number string and returns an int @@ -59,6 +62,18 @@ func ParsePort(rawPort string) (int, error) { return int(port), nil } +// ParsePortRange parses the port range string and returns start/end ints +func ParsePortRange(rawPort string) (int, int, error) { + if len(rawPort) == 0 { + return 0, 0, nil + } + start, end, err := parsers.ParsePortRange(rawPort) + if err != nil { + return 0, 0, err + } + return int(start), int(end), nil +} + // Proto returns the protocol of a Port func (p Port) Proto() string { proto, _ := SplitProtoPort(string(p)) @@ -84,6 +99,11 @@ func (p Port) Int() int { return int(port) } +// Range returns the start/end port numbers of a Port range as ints +func (p Port) Range() (int, int, error) { + return ParsePortRange(p.Port()) +} + // SplitProtoPort splits a port in the format of proto/port func SplitProtoPort(rawPort string) (string, string) { parts := strings.Split(rawPort, "/") @@ -162,7 +182,12 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, } if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) { - return nil, nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) + // Allow host port range iff containerPort is not a range. + // In this case, use the host port range as the dynamic + // host port range to allocate into. + if endPort != startPort { + return nil, nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) + } } if !validateProto(strings.ToLower(proto)) { @@ -174,6 +199,11 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, if len(hostPort) > 0 { hostPort = strconv.FormatUint(startHostPort+i, 10) } + // Set hostPort to a range only if there is a single container port + // and a dynamic host port. + if startPort == endPort && startHostPort != endHostPort { + hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10)) + } port, err := NewPort(strings.ToLower(proto), containerPort) if err != nil { return nil, nil, err diff --git a/nat/nat_test.go b/nat/nat_test.go index d9472cc..2c71142 100644 --- a/nat/nat_test.go +++ b/nat/nat_test.go @@ -41,6 +41,56 @@ func TestParsePort(t *testing.T) { } } +func TestParsePortRange(t *testing.T) { + var ( + begin int + end int + err error + ) + + type TestRange struct { + Range string + Begin int + End int + } + validRanges := []TestRange{ + {"1234", 1234, 1234}, + {"1234-1234", 1234, 1234}, + {"1234-1235", 1234, 1235}, + {"8000-9000", 8000, 9000}, + {"0", 0, 0}, + {"0-0", 0, 0}, + } + + for _, r := range validRanges { + begin, end, err = ParsePortRange(r.Range) + + if err != nil || begin != r.Begin { + t.Fatalf("Parsing port range '%s' did not succeed. Expected begin %d, got %d", r.Range, r.Begin, begin) + } + if err != nil || end != r.End { + t.Fatalf("Parsing port range '%s' did not succeed. Expected end %d, got %d", r.Range, r.End, end) + } + } + + invalidRanges := []string{ + "asdf", + "1asdf", + "9000-8000", + "9000-", + "-8000", + "-8000-", + } + + for _, r := range invalidRanges { + begin, end, err = ParsePortRange(r) + + if err == nil || begin != 0 || end != 0 { + t.Fatalf("Parsing port range '%s' succeeded", r) + } + } +} + func TestPort(t *testing.T) { p, err := NewPort("tcp", "1234") @@ -68,6 +118,20 @@ func TestPort(t *testing.T) { if err == nil { t.Fatal("tcp, asd1234 was supposed to fail") } + + p, err = NewPort("tcp", "1234-1230") + if err == nil { + t.Fatal("tcp, 1234-1230 was supposed to fail") + } + + p, err = NewPort("tcp", "1234-1242") + if err != nil { + t.Fatalf("tcp, 1234-1242 had a parsing issue: %v", err) + } + + if string(p) != "1234-1242/tcp" { + t.Fatal("tcp, 1234-1242 did not result in the string 1234-1242/tcp") + } } func TestSplitProtoPort(t *testing.T) { diff --git a/nat/sort.go b/nat/sort.go index 0a9dd07..1eb0fed 100644 --- a/nat/sort.go +++ b/nat/sort.go @@ -2,8 +2,9 @@ package nat import ( "sort" - "strconv" "strings" + + "github.com/docker/docker/pkg/parsers" ) type portSorter struct { @@ -88,8 +89,8 @@ func SortPortMap(ports []Port, bindings PortMap) { } } -func toInt(s string) int64 { - i, err := strconv.ParseInt(s, 10, 64) +func toInt(s string) uint64 { + i, _, err := parsers.ParsePortRange(s) if err != nil { i = 0 }