Skip to content

Commit 177d7fc

Browse files
authored
Merge pull request #173 from fahedouch/fix-port-range
expose port range
2 parents 56f348b + 459eba6 commit 177d7fc

File tree

3 files changed

+201
-66
lines changed

3 files changed

+201
-66
lines changed

pkg/portutil/portutil.go

+64-35
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,32 @@ package portutil
1818

1919
import (
2020
"net"
21-
"strconv"
2221
"strings"
2322

2423
gocni "github.com/containerd/go-cni"
24+
"github.com/docker/go-connections/nat"
2525
"github.com/pkg/errors"
2626
)
2727

28-
func ParseFlagP(s string) (*gocni.PortMapping, error) {
28+
//return respectively ip, hostPort, containerPort
29+
func splitParts(rawport string) (string, string, string) {
30+
parts := strings.Split(rawport, ":")
31+
n := len(parts)
32+
containerport := parts[n-1]
33+
34+
switch n {
35+
case 1:
36+
return "", "", containerport
37+
case 2:
38+
return "", parts[0], containerport
39+
case 3:
40+
return parts[0], parts[1], containerport
41+
default:
42+
return strings.Join(parts[:n-2], ":"), parts[n-2], containerport
43+
}
44+
}
45+
46+
func ParseFlagP(s string) ([]gocni.PortMapping, error) {
2947
proto := "tcp"
3048
splitBySlash := strings.Split(s, "/")
3149
switch len(splitBySlash) {
@@ -42,44 +60,55 @@ func ParseFlagP(s string) (*gocni.PortMapping, error) {
4260
return nil, errors.Errorf("failed to parse %q, unexpected slashes", s)
4361
}
4462

45-
res := &gocni.PortMapping{
63+
res := gocni.PortMapping{
4664
Protocol: proto,
47-
HostIP: "0.0.0.0",
4865
}
4966

50-
splitByColon := strings.Split(splitBySlash[0], ":")
51-
switch len(splitByColon) {
52-
case 1:
67+
mr := []gocni.PortMapping{}
68+
69+
ip, hostPort, containerPort := splitParts(splitBySlash[0])
70+
71+
if containerPort == "" {
72+
return nil, errors.Errorf("no port specified: %s", splitBySlash[0])
73+
}
74+
75+
if hostPort == "" {
5376
return nil, errors.Errorf("automatic host port assignment is not supported yet (FIXME)")
54-
case 2:
55-
i, err := strconv.Atoi(splitByColon[0])
56-
if err != nil {
57-
return nil, err
58-
}
59-
res.HostPort = int32(i)
60-
i, err = strconv.Atoi(splitByColon[1])
61-
if err != nil {
62-
return nil, err
63-
}
64-
res.ContainerPort = int32(i)
65-
return res, nil
66-
case 3:
67-
res.HostIP = splitByColon[0]
68-
if net.ParseIP(res.HostIP) == nil {
69-
return nil, errors.Errorf("invalid IP %q", res.HostIP)
70-
}
71-
i, err := strconv.Atoi(splitByColon[1])
72-
if err != nil {
73-
return nil, err
77+
}
78+
79+
startHostPort, endHostPort, err := nat.ParsePortRange(hostPort)
80+
if err != nil {
81+
return nil, errors.Errorf("invalid hostPort: %s", hostPort)
82+
}
83+
84+
startPort, endPort, err := nat.ParsePortRange(containerPort)
85+
if err != nil {
86+
return nil, errors.Errorf("invalid containerPort: %s", containerPort)
87+
}
88+
89+
if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
90+
if endPort != startPort {
91+
return nil, errors.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
7492
}
75-
res.HostPort = int32(i)
76-
i, err = strconv.Atoi(splitByColon[2])
77-
if err != nil {
78-
return nil, err
93+
}
94+
95+
for i := int32(0); i <= (int32(endPort) - int32(startPort)); i++ {
96+
97+
res.ContainerPort = int32(startPort) + i
98+
res.HostPort = int32(startHostPort) + i
99+
if ip == "" {
100+
//TODO handle ipv6
101+
res.HostIP = "0.0.0.0"
102+
} else {
103+
// TODO handle ipv6
104+
if net.ParseIP(ip) == nil {
105+
return nil, errors.Errorf("invalid ip address: %s", ip)
106+
}
107+
res.HostIP = ip
79108
}
80-
res.ContainerPort = int32(i)
81-
return res, nil
82-
default:
83-
return nil, errors.Errorf("failed to parse %q, unexpected colons", s)
109+
110+
mr = append(mr, res)
84111
}
112+
113+
return mr, nil
85114
}

run.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ func runAction(clicontext *cli.Context) error {
343343
portSlice := strutil.DedupeStrSlice(clicontext.StringSlice("p"))
344344
netSlice := strutil.DedupeStrSlice(clicontext.StringSlice("net"))
345345

346-
ports := make([]gocni.PortMapping, len(portSlice))
346+
ports := make([]gocni.PortMapping, 0)
347347
if len(netSlice) != 1 {
348348
return errors.New("currently, number of networks must be 1")
349349
}
@@ -384,12 +384,12 @@ func runAction(clicontext *cli.Context) error {
384384
return err
385385
}
386386
opts = append(opts, withCustomResolvConf(resolvConfPath), withCustomHosts(etcHostsPath))
387-
for i, p := range portSlice {
387+
for _, p := range portSlice {
388388
pm, err := portutil.ParseFlagP(p)
389389
if err != nil {
390390
return err
391391
}
392-
ports[i] = *pm
392+
ports = append(ports, pm...)
393393
}
394394
}
395395

run_network_test.go

+134-28
Original file line numberDiff line numberDiff line change
@@ -146,48 +146,147 @@ func TestRunPort(t *testing.T) {
146146
hostIP, err := getNonLoopbackIPv4()
147147
assert.NilError(t, err)
148148
type testCase struct {
149-
listenIP net.IP
150-
connectIP net.IP
151-
port int
152-
err string
149+
listenIP net.IP
150+
connectIP net.IP
151+
hostPort string
152+
containerPort string
153+
connectURLPort int
154+
runShouldSuccess bool
155+
err string
153156
}
154157
lo := net.ParseIP("127.0.0.1")
155158
zeroIP := net.ParseIP("0.0.0.0")
156159
testCases := []testCase{
157160
{
158-
listenIP: lo,
159-
connectIP: lo,
160-
port: 8080,
161+
listenIP: lo,
162+
connectIP: lo,
163+
hostPort: "8080",
164+
containerPort: "80",
165+
connectURLPort: 8080,
166+
runShouldSuccess: true,
161167
},
162168
{
163169
// for https://github.com/containerd/nerdctl/issues/88
164-
listenIP: hostIP,
165-
connectIP: hostIP,
166-
port: 8080,
170+
listenIP: hostIP,
171+
connectIP: hostIP,
172+
hostPort: "8080",
173+
containerPort: "80",
174+
connectURLPort: 8080,
175+
runShouldSuccess: true,
167176
},
168177
{
169-
listenIP: hostIP,
170-
connectIP: lo,
171-
port: 8080,
172-
err: "connection refused",
178+
listenIP: hostIP,
179+
connectIP: lo,
180+
hostPort: "8080",
181+
containerPort: "80",
182+
connectURLPort: 8080,
183+
err: "connection refused",
184+
runShouldSuccess: true,
173185
},
174186
{
175-
listenIP: lo,
176-
connectIP: hostIP,
177-
port: 8080,
178-
err: "connection refused",
187+
listenIP: lo,
188+
connectIP: hostIP,
189+
hostPort: "8080",
190+
containerPort: "80",
191+
connectURLPort: 8080,
192+
err: "connection refused",
193+
runShouldSuccess: true,
179194
},
180195
{
181-
listenIP: zeroIP,
182-
connectIP: lo,
183-
port: 8080,
196+
listenIP: zeroIP,
197+
connectIP: lo,
198+
hostPort: "8080",
199+
containerPort: "80",
200+
connectURLPort: 8080,
201+
runShouldSuccess: true,
184202
},
185203
{
186-
listenIP: zeroIP,
187-
connectIP: hostIP,
188-
port: 8080,
204+
listenIP: zeroIP,
205+
connectIP: hostIP,
206+
hostPort: "8080",
207+
containerPort: "80",
208+
connectURLPort: 8080,
209+
runShouldSuccess: true,
210+
},
211+
{
212+
listenIP: lo,
213+
connectIP: lo,
214+
hostPort: "7000-7005",
215+
containerPort: "79-84",
216+
connectURLPort: 7001,
217+
runShouldSuccess: true,
218+
},
219+
{
220+
listenIP: hostIP,
221+
connectIP: hostIP,
222+
hostPort: "7000-7005",
223+
containerPort: "79-84",
224+
connectURLPort: 7001,
225+
runShouldSuccess: true,
226+
},
227+
{
228+
listenIP: hostIP,
229+
connectIP: lo,
230+
hostPort: "7000-7005",
231+
containerPort: "79-84",
232+
connectURLPort: 7001,
233+
err: "connection refused",
234+
runShouldSuccess: true,
235+
},
236+
{
237+
listenIP: lo,
238+
connectIP: hostIP,
239+
hostPort: "7000-7005",
240+
containerPort: "79-84",
241+
connectURLPort: 7001,
242+
err: "connection refused",
243+
runShouldSuccess: true,
244+
},
245+
{
246+
listenIP: zeroIP,
247+
connectIP: hostIP,
248+
hostPort: "7000-7005",
249+
containerPort: "79-84",
250+
connectURLPort: 7001,
251+
runShouldSuccess: true,
252+
},
253+
{
254+
listenIP: zeroIP,
255+
connectIP: lo,
256+
hostPort: "7000-7005",
257+
containerPort: "80-85",
258+
connectURLPort: 7001,
259+
err: "error after 30 attempts",
260+
runShouldSuccess: true,
261+
},
262+
{
263+
listenIP: zeroIP,
264+
connectIP: lo,
265+
hostPort: "7000-7005",
266+
containerPort: "80",
267+
connectURLPort: 7000,
268+
runShouldSuccess: true,
269+
},
270+
{
271+
listenIP: zeroIP,
272+
connectIP: lo,
273+
hostPort: "7000-7005",
274+
containerPort: "80",
275+
connectURLPort: 7005,
276+
err: "connection refused",
277+
runShouldSuccess: true,
278+
},
279+
{
280+
listenIP: zeroIP,
281+
connectIP: lo,
282+
hostPort: "7000-7005",
283+
containerPort: "79-85",
284+
connectURLPort: 7005,
285+
err: "invalid ranges specified for container and host Ports",
286+
runShouldSuccess: false,
189287
},
190288
}
289+
191290
for i, tc := range testCases {
192291
i := i
193292
tc := tc
@@ -196,13 +295,19 @@ func TestRunPort(t *testing.T) {
196295
testContainerName := fmt.Sprintf("nerdctl-test-nginx-%d", i)
197296
base := testutil.NewBase(t)
198297
defer base.Cmd("rm", "-f", testContainerName).Run()
199-
pFlag := fmt.Sprintf("%s:%d:80", tc.listenIP.String(), tc.port)
200-
connectURL := fmt.Sprintf("http://%s:%d", tc.connectIP.String(), tc.port)
298+
pFlag := fmt.Sprintf("%s:%s:%s", tc.listenIP.String(), tc.hostPort, tc.containerPort)
299+
connectURL := fmt.Sprintf("http://%s:%d", tc.connectIP.String(), tc.connectURLPort)
201300
t.Logf("pFlag=%q, connectURL=%q", pFlag, connectURL)
202-
base.Cmd("run", "-d",
301+
cmd := base.Cmd("run", "-d",
203302
"--name", testContainerName,
204303
"-p", pFlag,
205-
testutil.NginxAlpineImage).AssertOK()
304+
testutil.NginxAlpineImage)
305+
if tc.runShouldSuccess {
306+
cmd.AssertOK()
307+
} else {
308+
cmd.AssertFail()
309+
return
310+
}
206311

207312
resp, err := httpGet(connectURL, 30)
208313
if tc.err != "" {
@@ -215,6 +320,7 @@ func TestRunPort(t *testing.T) {
215320
assert.Assert(t, strings.Contains(string(respBody), testutil.NginxAlpineIndexHTMLSnippet))
216321
})
217322
}
323+
218324
}
219325

220326
func httpGet(urlStr string, attempts int) (*http.Response, error) {

0 commit comments

Comments
 (0)