github.com/goern/docker@v1.9.0-rc1/pkg/nat/nat.go (about)

     1  package nat
     2  
     3  // nat is a convenience package for docker's manipulation of strings describing
     4  // network ports.
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/docker/docker/pkg/parsers"
    13  )
    14  
    15  const (
    16  	// portSpecTemplate is the expected format for port specifications
    17  	portSpecTemplate = "ip:hostPort:containerPort"
    18  )
    19  
    20  // PortBinding represents a binding between a Host IP address and a Host Port
    21  type PortBinding struct {
    22  	// HostIP is the host IP Address
    23  	HostIP string `json:"HostIp"`
    24  	// HostPort is the host port number
    25  	HostPort string
    26  }
    27  
    28  // PortMap is a collection of PortBinding indexed by Port
    29  type PortMap map[Port][]PortBinding
    30  
    31  // PortSet is a collection of structs indexed by Port
    32  type PortSet map[Port]struct{}
    33  
    34  // Port is a string containing port number and protocol in the format "80/tcp"
    35  type Port string
    36  
    37  // NewPort creates a new instance of a Port given a protocol and port number or port range
    38  func NewPort(proto, port string) (Port, error) {
    39  	// Check for parsing issues on "port" now so we can avoid having
    40  	// to check it later on.
    41  
    42  	portStartInt, portEndInt, err := ParsePortRange(port)
    43  	if err != nil {
    44  		return "", err
    45  	}
    46  
    47  	if portStartInt == portEndInt {
    48  		return Port(fmt.Sprintf("%d/%s", portStartInt, proto)), nil
    49  	}
    50  	return Port(fmt.Sprintf("%d-%d/%s", portStartInt, portEndInt, proto)), nil
    51  }
    52  
    53  // ParsePort parses the port number string and returns an int
    54  func ParsePort(rawPort string) (int, error) {
    55  	if len(rawPort) == 0 {
    56  		return 0, nil
    57  	}
    58  	port, err := strconv.ParseUint(rawPort, 10, 16)
    59  	if err != nil {
    60  		return 0, err
    61  	}
    62  	return int(port), nil
    63  }
    64  
    65  // ParsePortRange parses the port range string and returns start/end ints
    66  func ParsePortRange(rawPort string) (int, int, error) {
    67  	if len(rawPort) == 0 {
    68  		return 0, 0, nil
    69  	}
    70  	start, end, err := parsers.ParsePortRange(rawPort)
    71  	if err != nil {
    72  		return 0, 0, err
    73  	}
    74  	return int(start), int(end), nil
    75  }
    76  
    77  // Proto returns the protocol of a Port
    78  func (p Port) Proto() string {
    79  	proto, _ := SplitProtoPort(string(p))
    80  	return proto
    81  }
    82  
    83  // Port returns the port number of a Port
    84  func (p Port) Port() string {
    85  	_, port := SplitProtoPort(string(p))
    86  	return port
    87  }
    88  
    89  // Int returns the port number of a Port as an int
    90  func (p Port) Int() int {
    91  	portStr := p.Port()
    92  	if len(portStr) == 0 {
    93  		return 0
    94  	}
    95  
    96  	// We don't need to check for an error because we're going to
    97  	// assume that any error would have been found, and reported, in NewPort()
    98  	port, _ := strconv.ParseUint(portStr, 10, 16)
    99  	return int(port)
   100  }
   101  
   102  // Range returns the start/end port numbers of a Port range as ints
   103  func (p Port) Range() (int, int, error) {
   104  	return ParsePortRange(p.Port())
   105  }
   106  
   107  // SplitProtoPort splits a port in the format of proto/port
   108  func SplitProtoPort(rawPort string) (string, string) {
   109  	parts := strings.Split(rawPort, "/")
   110  	l := len(parts)
   111  	if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 {
   112  		return "", ""
   113  	}
   114  	if l == 1 {
   115  		return "tcp", rawPort
   116  	}
   117  	if len(parts[1]) == 0 {
   118  		return "tcp", parts[0]
   119  	}
   120  	return parts[1], parts[0]
   121  }
   122  
   123  func validateProto(proto string) bool {
   124  	for _, availableProto := range []string{"tcp", "udp"} {
   125  		if availableProto == proto {
   126  			return true
   127  		}
   128  	}
   129  	return false
   130  }
   131  
   132  // ParsePortSpecs receives port specs in the format of ip:public:private/proto and parses
   133  // these in to the internal types
   134  func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) {
   135  	var (
   136  		exposedPorts = make(map[Port]struct{}, len(ports))
   137  		bindings     = make(map[Port][]PortBinding)
   138  	)
   139  
   140  	for _, rawPort := range ports {
   141  		proto := "tcp"
   142  
   143  		if i := strings.LastIndex(rawPort, "/"); i != -1 {
   144  			proto = rawPort[i+1:]
   145  			rawPort = rawPort[:i]
   146  		}
   147  		if !strings.Contains(rawPort, ":") {
   148  			rawPort = fmt.Sprintf("::%s", rawPort)
   149  		} else if len(strings.Split(rawPort, ":")) == 2 {
   150  			rawPort = fmt.Sprintf(":%s", rawPort)
   151  		}
   152  
   153  		parts, err := parsers.PartParser(portSpecTemplate, rawPort)
   154  		if err != nil {
   155  			return nil, nil, err
   156  		}
   157  
   158  		var (
   159  			containerPort = parts["containerPort"]
   160  			rawIP         = parts["ip"]
   161  			hostPort      = parts["hostPort"]
   162  		)
   163  
   164  		if rawIP != "" && net.ParseIP(rawIP) == nil {
   165  			return nil, nil, fmt.Errorf("Invalid ip address: %s", rawIP)
   166  		}
   167  		if containerPort == "" {
   168  			return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
   169  		}
   170  
   171  		startPort, endPort, err := parsers.ParsePortRange(containerPort)
   172  		if err != nil {
   173  			return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort)
   174  		}
   175  
   176  		var startHostPort, endHostPort uint64 = 0, 0
   177  		if len(hostPort) > 0 {
   178  			startHostPort, endHostPort, err = parsers.ParsePortRange(hostPort)
   179  			if err != nil {
   180  				return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort)
   181  			}
   182  		}
   183  
   184  		if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
   185  			// Allow host port range iff containerPort is not a range.
   186  			// In this case, use the host port range as the dynamic
   187  			// host port range to allocate into.
   188  			if endPort != startPort {
   189  				return nil, nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
   190  			}
   191  		}
   192  
   193  		if !validateProto(strings.ToLower(proto)) {
   194  			return nil, nil, fmt.Errorf("Invalid proto: %s", proto)
   195  		}
   196  
   197  		for i := uint64(0); i <= (endPort - startPort); i++ {
   198  			containerPort = strconv.FormatUint(startPort+i, 10)
   199  			if len(hostPort) > 0 {
   200  				hostPort = strconv.FormatUint(startHostPort+i, 10)
   201  			}
   202  			// Set hostPort to a range only if there is a single container port
   203  			// and a dynamic host port.
   204  			if startPort == endPort && startHostPort != endHostPort {
   205  				hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10))
   206  			}
   207  			port, err := NewPort(strings.ToLower(proto), containerPort)
   208  			if err != nil {
   209  				return nil, nil, err
   210  			}
   211  			if _, exists := exposedPorts[port]; !exists {
   212  				exposedPorts[port] = struct{}{}
   213  			}
   214  
   215  			binding := PortBinding{
   216  				HostIP:   rawIP,
   217  				HostPort: hostPort,
   218  			}
   219  			bslice, exists := bindings[port]
   220  			if !exists {
   221  				bslice = []PortBinding{}
   222  			}
   223  			bindings[port] = append(bslice, binding)
   224  		}
   225  	}
   226  	return exposedPorts, bindings, nil
   227  }