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