...

Source file src/github.com/docker/go-connections/nat/nat.go

Documentation: github.com/docker/go-connections/nat

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

View as plain text