1
2 package nat
3
4 import (
5 "fmt"
6 "net"
7 "strconv"
8 "strings"
9 )
10
11
12 type PortBinding struct {
13
14 HostIP string `json:"HostIp"`
15
16 HostPort string
17 }
18
19
20 type PortMap map[Port][]PortBinding
21
22
23 type PortSet map[Port]struct{}
24
25
26 type Port string
27
28
29 func NewPort(proto, port string) (Port, error) {
30
31
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
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
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
69 func (p Port) Proto() string {
70 proto, _ := SplitProtoPort(string(p))
71 return proto
72 }
73
74
75 func (p Port) Port() string {
76 _, port := SplitProtoPort(string(p))
77 return port
78 }
79
80
81 func (p Port) Int() int {
82 portStr := p.Port()
83
84
85 port, _ := ParsePort(portStr)
86 return port
87 }
88
89
90 func (p Port) Range() (int, int, error) {
91 return ParsePortRangeToInt(p.Port())
92 }
93
94
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
120
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
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
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
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
206
207
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
224
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