From f0771d5bbbef48db1e21633893d63bf5f004f7bc Mon Sep 17 00:00:00 2001
From: Peter Waller
Date: Tue, 30 Jun 2015 17:41:01 +0100
Subject: [PATCH] Move /nat to /pkg/nat
By convention /pkg is safe to use from outside the docker tree, for example
if you're building a docker orchestrator.
/nat currently doesn't have any dependencies outside of /pkg, so it seems
reasonable to move it there.
This rename was performed with:
```
gomvpkg -vcs_mv_cmd="git mv {{.Src}} {{.Dst}}" \
-from github.com/docker/docker/nat \
-to github.com/docker/docker/pkg/nat
```
Signed-off-by: Peter Waller
---
nat/nat.go | 172 ++++++++++++++++++++++++++++
nat/nat_test.go | 293 +++++++++++++++++++++++++++++++++++++++++++++++
nat/sort.go | 94 +++++++++++++++
nat/sort_test.go | 85 ++++++++++++++
4 files changed, 644 insertions(+)
create mode 100644 nat/nat.go
create mode 100644 nat/nat_test.go
create mode 100644 nat/sort.go
create mode 100644 nat/sort_test.go
diff --git a/nat/nat.go b/nat/nat.go
new file mode 100644
index 0000000..2cec2e8
--- /dev/null
+++ b/nat/nat.go
@@ -0,0 +1,172 @@
+package nat
+
+// nat is a convenience package for docker's manipulation of strings describing
+// network ports.
+
+import (
+ "fmt"
+ "net"
+ "strconv"
+ "strings"
+
+ "github.com/docker/docker/pkg/parsers"
+)
+
+const (
+ PortSpecTemplate = "ip:hostPort:containerPort"
+ PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort"
+)
+
+type PortBinding struct {
+ HostIp string
+ HostPort string
+}
+
+type PortMap map[Port][]PortBinding
+
+type PortSet map[Port]struct{}
+
+// 80/tcp
+type Port string
+
+func NewPort(proto, port string) Port {
+ return Port(fmt.Sprintf("%s/%s", port, proto))
+}
+
+func ParsePort(rawPort string) (int, error) {
+ if len(rawPort) == 0 {
+ return 0, nil
+ }
+ port, err := strconv.ParseUint(rawPort, 10, 16)
+ if err != nil {
+ return 0, err
+ }
+ return int(port), nil
+}
+
+func (p Port) Proto() string {
+ proto, _ := SplitProtoPort(string(p))
+ return proto
+}
+
+func (p Port) Port() string {
+ _, port := SplitProtoPort(string(p))
+ return port
+}
+
+func (p Port) Int() int {
+ port, err := ParsePort(p.Port())
+ if err != nil {
+ panic(err)
+ }
+ return port
+}
+
+// Splits a port in the format of proto/port
+func SplitProtoPort(rawPort string) (string, string) {
+ parts := strings.Split(rawPort, "/")
+ l := len(parts)
+ if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 {
+ return "", ""
+ }
+ if l == 1 {
+ return "tcp", rawPort
+ }
+ if len(parts[1]) == 0 {
+ return "tcp", parts[0]
+ }
+ return parts[1], parts[0]
+}
+
+func validateProto(proto string) bool {
+ for _, availableProto := range []string{"tcp", "udp"} {
+ if availableProto == proto {
+ return true
+ }
+ }
+ return false
+}
+
+// We will receive port specs in the format of ip:public:private/proto and these need to be
+// parsed in the internal types
+func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) {
+ var (
+ exposedPorts = make(map[Port]struct{}, len(ports))
+ bindings = make(map[Port][]PortBinding)
+ )
+
+ for _, rawPort := range ports {
+ proto := "tcp"
+
+ if i := strings.LastIndex(rawPort, "/"); i != -1 {
+ proto = rawPort[i+1:]
+ rawPort = rawPort[:i]
+ }
+ if !strings.Contains(rawPort, ":") {
+ rawPort = fmt.Sprintf("::%s", rawPort)
+ } else if len(strings.Split(rawPort, ":")) == 2 {
+ rawPort = fmt.Sprintf(":%s", rawPort)
+ }
+
+ parts, err := parsers.PartParser(PortSpecTemplate, rawPort)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var (
+ containerPort = parts["containerPort"]
+ rawIp = parts["ip"]
+ hostPort = parts["hostPort"]
+ )
+
+ if rawIp != "" && net.ParseIP(rawIp) == nil {
+ return nil, nil, fmt.Errorf("Invalid ip address: %s", rawIp)
+ }
+ if containerPort == "" {
+ return nil, nil, fmt.Errorf("No port specified: %s", rawPort)
+ }
+
+ startPort, endPort, err := parsers.ParsePortRange(containerPort)
+ if err != nil {
+ return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort)
+ }
+
+ var startHostPort, endHostPort uint64 = 0, 0
+ if len(hostPort) > 0 {
+ startHostPort, endHostPort, err = parsers.ParsePortRange(hostPort)
+ if err != nil {
+ return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort)
+ }
+ }
+
+ if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
+ return nil, nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
+ }
+
+ if !validateProto(strings.ToLower(proto)) {
+ return nil, nil, fmt.Errorf("Invalid proto: %s", proto)
+ }
+
+ for i := uint64(0); i <= (endPort - startPort); i++ {
+ containerPort = strconv.FormatUint(startPort+i, 10)
+ if len(hostPort) > 0 {
+ hostPort = strconv.FormatUint(startHostPort+i, 10)
+ }
+ port := NewPort(strings.ToLower(proto), containerPort)
+ if _, exists := exposedPorts[port]; !exists {
+ exposedPorts[port] = struct{}{}
+ }
+
+ binding := PortBinding{
+ HostIp: rawIp,
+ HostPort: hostPort,
+ }
+ bslice, exists := bindings[port]
+ if !exists {
+ bslice = []PortBinding{}
+ }
+ bindings[port] = append(bslice, binding)
+ }
+ }
+ return exposedPorts, bindings, nil
+}
diff --git a/nat/nat_test.go b/nat/nat_test.go
new file mode 100644
index 0000000..376857f
--- /dev/null
+++ b/nat/nat_test.go
@@ -0,0 +1,293 @@
+package nat
+
+import (
+ "testing"
+)
+
+func TestParsePort(t *testing.T) {
+ var (
+ p int
+ err error
+ )
+
+ p, err = ParsePort("1234")
+
+ if err != nil || p != 1234 {
+ t.Fatal("Parsing '1234' did not succeed")
+ }
+
+ // FIXME currently this is a valid port. I don't think it should be.
+ // I'm leaving this test commented out until we make a decision.
+ // - erikh
+
+ /*
+ p, err = ParsePort("0123")
+
+ if err != nil {
+ t.Fatal("Successfully parsed port '0123' to '123'")
+ }
+ */
+
+ p, err = ParsePort("asdf")
+
+ if err == nil || p != 0 {
+ t.Fatal("Parsing port 'asdf' succeeded")
+ }
+
+ p, err = ParsePort("1asdf")
+
+ if err == nil || p != 0 {
+ t.Fatal("Parsing port '1asdf' succeeded")
+ }
+}
+
+func TestPort(t *testing.T) {
+ p := NewPort("tcp", "1234")
+
+ if string(p) != "1234/tcp" {
+ t.Fatal("tcp, 1234 did not result in the string 1234/tcp")
+ }
+
+ if p.Proto() != "tcp" {
+ t.Fatal("protocol was not tcp")
+ }
+
+ if p.Port() != "1234" {
+ t.Fatal("port string value was not 1234")
+ }
+
+ if p.Int() != 1234 {
+ t.Fatal("port int value was not 1234")
+ }
+}
+
+func TestSplitProtoPort(t *testing.T) {
+ var (
+ proto string
+ port string
+ )
+
+ proto, port = SplitProtoPort("1234/tcp")
+
+ if proto != "tcp" || port != "1234" {
+ t.Fatal("Could not split 1234/tcp properly")
+ }
+
+ proto, port = SplitProtoPort("")
+
+ if proto != "" || port != "" {
+ t.Fatal("parsing an empty string yielded surprising results", proto, port)
+ }
+
+ proto, port = SplitProtoPort("1234")
+
+ if proto != "tcp" || port != "1234" {
+ t.Fatal("tcp is not the default protocol for portspec '1234'", proto, port)
+ }
+
+ proto, port = SplitProtoPort("1234/")
+
+ if proto != "tcp" || port != "1234" {
+ t.Fatal("parsing '1234/' yielded:" + port + "/" + proto)
+ }
+
+ proto, port = SplitProtoPort("/tcp")
+
+ if proto != "" || port != "" {
+ t.Fatal("parsing '/tcp' yielded:" + port + "/" + proto)
+ }
+}
+
+func TestParsePortSpecs(t *testing.T) {
+ var (
+ portMap map[Port]struct{}
+ bindingMap map[Port][]PortBinding
+ err error
+ )
+
+ portMap, bindingMap, err = ParsePortSpecs([]string{"1234/tcp", "2345/udp"})
+
+ if err != nil {
+ t.Fatalf("Error while processing ParsePortSpecs: %s", err)
+ }
+
+ if _, ok := portMap[Port("1234/tcp")]; !ok {
+ t.Fatal("1234/tcp was not parsed properly")
+ }
+
+ if _, ok := portMap[Port("2345/udp")]; !ok {
+ t.Fatal("2345/udp was not parsed properly")
+ }
+
+ for portspec, bindings := range bindingMap {
+ if len(bindings) != 1 {
+ t.Fatalf("%s should have exactly one binding", portspec)
+ }
+
+ if bindings[0].HostIp != "" {
+ t.Fatalf("HostIp should not be set for %s", portspec)
+ }
+
+ if bindings[0].HostPort != "" {
+ t.Fatalf("HostPort should not be set for %s", portspec)
+ }
+ }
+
+ portMap, bindingMap, err = ParsePortSpecs([]string{"1234:1234/tcp", "2345:2345/udp"})
+
+ if err != nil {
+ t.Fatalf("Error while processing ParsePortSpecs: %s", err)
+ }
+
+ if _, ok := portMap[Port("1234/tcp")]; !ok {
+ t.Fatal("1234/tcp was not parsed properly")
+ }
+
+ if _, ok := portMap[Port("2345/udp")]; !ok {
+ t.Fatal("2345/udp was not parsed properly")
+ }
+
+ for portspec, bindings := range bindingMap {
+ _, port := SplitProtoPort(string(portspec))
+
+ if len(bindings) != 1 {
+ t.Fatalf("%s should have exactly one binding", portspec)
+ }
+
+ if bindings[0].HostIp != "" {
+ t.Fatalf("HostIp should not be set for %s", portspec)
+ }
+
+ if bindings[0].HostPort != port {
+ t.Fatalf("HostPort should be %s for %s", port, portspec)
+ }
+ }
+
+ portMap, bindingMap, err = ParsePortSpecs([]string{"0.0.0.0:1234:1234/tcp", "0.0.0.0:2345:2345/udp"})
+
+ if err != nil {
+ t.Fatalf("Error while processing ParsePortSpecs: %s", err)
+ }
+
+ if _, ok := portMap[Port("1234/tcp")]; !ok {
+ t.Fatal("1234/tcp was not parsed properly")
+ }
+
+ if _, ok := portMap[Port("2345/udp")]; !ok {
+ t.Fatal("2345/udp was not parsed properly")
+ }
+
+ for portspec, bindings := range bindingMap {
+ _, port := SplitProtoPort(string(portspec))
+
+ if len(bindings) != 1 {
+ t.Fatalf("%s should have exactly one binding", portspec)
+ }
+
+ if bindings[0].HostIp != "0.0.0.0" {
+ t.Fatalf("HostIp is not 0.0.0.0 for %s", portspec)
+ }
+
+ if bindings[0].HostPort != port {
+ t.Fatalf("HostPort should be %s for %s", port, portspec)
+ }
+ }
+
+ _, _, err = ParsePortSpecs([]string{"localhost:1234:1234/tcp"})
+
+ if err == nil {
+ t.Fatal("Received no error while trying to parse a hostname instead of ip")
+ }
+}
+
+func TestParsePortSpecsWithRange(t *testing.T) {
+ var (
+ portMap map[Port]struct{}
+ bindingMap map[Port][]PortBinding
+ err error
+ )
+
+ portMap, bindingMap, err = ParsePortSpecs([]string{"1234-1236/tcp", "2345-2347/udp"})
+
+ if err != nil {
+ t.Fatalf("Error while processing ParsePortSpecs: %s", err)
+ }
+
+ if _, ok := portMap[Port("1235/tcp")]; !ok {
+ t.Fatal("1234/tcp was not parsed properly")
+ }
+
+ if _, ok := portMap[Port("2346/udp")]; !ok {
+ t.Fatal("2345/udp was not parsed properly")
+ }
+
+ for portspec, bindings := range bindingMap {
+ if len(bindings) != 1 {
+ t.Fatalf("%s should have exactly one binding", portspec)
+ }
+
+ if bindings[0].HostIp != "" {
+ t.Fatalf("HostIp should not be set for %s", portspec)
+ }
+
+ if bindings[0].HostPort != "" {
+ t.Fatalf("HostPort should not be set for %s", portspec)
+ }
+ }
+
+ portMap, bindingMap, err = ParsePortSpecs([]string{"1234-1236:1234-1236/tcp", "2345-2347:2345-2347/udp"})
+
+ if err != nil {
+ t.Fatalf("Error while processing ParsePortSpecs: %s", err)
+ }
+
+ if _, ok := portMap[Port("1235/tcp")]; !ok {
+ t.Fatal("1234/tcp was not parsed properly")
+ }
+
+ if _, ok := portMap[Port("2346/udp")]; !ok {
+ t.Fatal("2345/udp was not parsed properly")
+ }
+
+ for portspec, bindings := range bindingMap {
+ _, port := SplitProtoPort(string(portspec))
+ if len(bindings) != 1 {
+ t.Fatalf("%s should have exactly one binding", portspec)
+ }
+
+ if bindings[0].HostIp != "" {
+ t.Fatalf("HostIp should not be set for %s", portspec)
+ }
+
+ if bindings[0].HostPort != port {
+ t.Fatalf("HostPort should be %s for %s", port, portspec)
+ }
+ }
+
+ portMap, bindingMap, err = ParsePortSpecs([]string{"0.0.0.0:1234-1236:1234-1236/tcp", "0.0.0.0:2345-2347:2345-2347/udp"})
+
+ if err != nil {
+ t.Fatalf("Error while processing ParsePortSpecs: %s", err)
+ }
+
+ if _, ok := portMap[Port("1235/tcp")]; !ok {
+ t.Fatal("1234/tcp was not parsed properly")
+ }
+
+ if _, ok := portMap[Port("2346/udp")]; !ok {
+ t.Fatal("2345/udp was not parsed properly")
+ }
+
+ for portspec, bindings := range bindingMap {
+ _, port := SplitProtoPort(string(portspec))
+ if len(bindings) != 1 || bindings[0].HostIp != "0.0.0.0" || bindings[0].HostPort != port {
+ t.Fatalf("Expect single binding to port %s but found %s", port, bindings)
+ }
+ }
+
+ _, _, err = ParsePortSpecs([]string{"localhost:1234-1236:1234-1236/tcp"})
+
+ if err == nil {
+ t.Fatal("Received no error while trying to parse a hostname instead of ip")
+ }
+}
diff --git a/nat/sort.go b/nat/sort.go
new file mode 100644
index 0000000..fa584c1
--- /dev/null
+++ b/nat/sort.go
@@ -0,0 +1,94 @@
+package nat
+
+import (
+ "sort"
+ "strconv"
+ "strings"
+)
+
+type portSorter struct {
+ ports []Port
+ by func(i, j Port) bool
+}
+
+func (s *portSorter) Len() int {
+ return len(s.ports)
+}
+
+func (s *portSorter) Swap(i, j int) {
+ s.ports[i], s.ports[j] = s.ports[j], s.ports[i]
+}
+
+func (s *portSorter) Less(i, j int) bool {
+ ip := s.ports[i]
+ jp := s.ports[j]
+
+ return s.by(ip, jp)
+}
+
+func Sort(ports []Port, predicate func(i, j Port) bool) {
+ s := &portSorter{ports, predicate}
+ sort.Sort(s)
+}
+
+type portMapEntry struct {
+ port Port
+ binding PortBinding
+}
+
+type portMapSorter []portMapEntry
+
+func (s portMapSorter) Len() int { return len(s) }
+func (s portMapSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+// sort the port so that the order is:
+// 1. port with larger specified bindings
+// 2. larger port
+// 3. port with tcp protocol
+func (s portMapSorter) Less(i, j int) bool {
+ pi, pj := s[i].port, s[j].port
+ hpi, hpj := toInt(s[i].binding.HostPort), toInt(s[j].binding.HostPort)
+ return hpi > hpj || pi.Int() > pj.Int() || (pi.Int() == pj.Int() && strings.ToLower(pi.Proto()) == "tcp")
+}
+
+// SortPortMap sorts the list of ports and their respected mapping. The ports
+// will explicit HostPort will be placed first.
+func SortPortMap(ports []Port, bindings PortMap) {
+ s := portMapSorter{}
+ for _, p := range ports {
+ if binding, ok := bindings[p]; ok {
+ for _, b := range binding {
+ s = append(s, portMapEntry{port: p, binding: b})
+ }
+ bindings[p] = []PortBinding{}
+ } else {
+ s = append(s, portMapEntry{port: p})
+ }
+ }
+
+ sort.Sort(s)
+ var (
+ i int
+ pm = make(map[Port]struct{})
+ )
+ // reorder ports
+ for _, entry := range s {
+ if _, ok := pm[entry.port]; !ok {
+ ports[i] = entry.port
+ pm[entry.port] = struct{}{}
+ i++
+ }
+ // reorder bindings for this port
+ if _, ok := bindings[entry.port]; ok {
+ bindings[entry.port] = append(bindings[entry.port], entry.binding)
+ }
+ }
+}
+
+func toInt(s string) int64 {
+ i, err := strconv.ParseInt(s, 10, 64)
+ if err != nil {
+ i = 0
+ }
+ return i
+}
diff --git a/nat/sort_test.go b/nat/sort_test.go
new file mode 100644
index 0000000..ba24cdb
--- /dev/null
+++ b/nat/sort_test.go
@@ -0,0 +1,85 @@
+package nat
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+)
+
+func TestSortUniquePorts(t *testing.T) {
+ ports := []Port{
+ Port("6379/tcp"),
+ Port("22/tcp"),
+ }
+
+ Sort(ports, func(ip, jp Port) bool {
+ return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp")
+ })
+
+ first := ports[0]
+ if fmt.Sprint(first) != "22/tcp" {
+ t.Log(fmt.Sprint(first))
+ t.Fail()
+ }
+}
+
+func TestSortSamePortWithDifferentProto(t *testing.T) {
+ ports := []Port{
+ Port("8888/tcp"),
+ Port("8888/udp"),
+ Port("6379/tcp"),
+ Port("6379/udp"),
+ }
+
+ Sort(ports, func(ip, jp Port) bool {
+ return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp")
+ })
+
+ first := ports[0]
+ if fmt.Sprint(first) != "6379/tcp" {
+ t.Fail()
+ }
+}
+
+func TestSortPortMap(t *testing.T) {
+ ports := []Port{
+ Port("22/tcp"),
+ Port("22/udp"),
+ Port("8000/tcp"),
+ Port("6379/tcp"),
+ Port("9999/tcp"),
+ }
+
+ portMap := PortMap{
+ Port("22/tcp"): []PortBinding{
+ {},
+ },
+ Port("8000/tcp"): []PortBinding{
+ {},
+ },
+ Port("6379/tcp"): []PortBinding{
+ {},
+ {HostIp: "0.0.0.0", HostPort: "32749"},
+ },
+ Port("9999/tcp"): []PortBinding{
+ {HostIp: "0.0.0.0", HostPort: "40000"},
+ },
+ }
+
+ SortPortMap(ports, portMap)
+ if !reflect.DeepEqual(ports, []Port{
+ Port("9999/tcp"),
+ Port("6379/tcp"),
+ Port("8000/tcp"),
+ Port("22/tcp"),
+ Port("22/udp"),
+ }) {
+ t.Errorf("failed to prioritize port with explicit mappings, got %v", ports)
+ }
+ if pm := portMap[Port("6379/tcp")]; !reflect.DeepEqual(pm, []PortBinding{
+ {HostIp: "0.0.0.0", HostPort: "32749"},
+ {},
+ }) {
+ t.Errorf("failed to prioritize bindings with explicit mappings, got %v", pm)
+ }
+}